diff mbox

[v2,6/8] drm/xen-front: Introduce DRM/KMS virtual display driver

Message ID 1520947867-32514-7-git-send-email-andr2000@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Oleksandr Andrushchenko March 13, 2018, 1:31 p.m. UTC
From: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>

Implement essential initialization of the display driver:
  - introduce required data structures
  - handle DRM/KMS driver registration
  - register driver on backend connection
  - remove driver on backend disconnect

Implement kernel modesetiing/connector handling using
DRM simple KMS helper pipeline:

- implement KMS part of the driver with the help of DRM
  simple pipepline helper which is possible due to the fact
  that the para-virtualized driver only supports a single
  (primary) plane:
  - initialize connectors according to XenStore configuration
  - handle frame done events from the backend
  - create and destroy frame buffers and propagate those
    to the backend
  - propagate set/reset mode configuration to the backend on display
    enable/disable callbacks
  - send page flip request to the backend and implement logic for
    reporting backend IO errors on prepare fb callback

- implement virtual connector handling:
  - support only pixel formats suitable for single plane modes
  - make sure the connector is always connected
  - support a single video mode as per para-virtualized driver
    configuration

Signed-off-by: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
---
 drivers/gpu/drm/xen/Makefile                |   3 +
 drivers/gpu/drm/xen/xen_drm_front.c         | 161 +++++++++++++++-
 drivers/gpu/drm/xen/xen_drm_front.h         |  32 ++++
 drivers/gpu/drm/xen/xen_drm_front_conn.c    | 119 ++++++++++++
 drivers/gpu/drm/xen/xen_drm_front_conn.h    |  27 +++
 drivers/gpu/drm/xen/xen_drm_front_drv.c     | 171 +++++++++++++++++
 drivers/gpu/drm/xen/xen_drm_front_drv.h     |  65 +++++++
 drivers/gpu/drm/xen/xen_drm_front_evtchnl.c |   4 +-
 drivers/gpu/drm/xen/xen_drm_front_kms.c     | 282 ++++++++++++++++++++++++++++
 drivers/gpu/drm/xen/xen_drm_front_kms.h     |  22 +++
 10 files changed, 883 insertions(+), 3 deletions(-)
 create mode 100644 drivers/gpu/drm/xen/xen_drm_front_conn.c
 create mode 100644 drivers/gpu/drm/xen/xen_drm_front_conn.h
 create mode 100644 drivers/gpu/drm/xen/xen_drm_front_drv.c
 create mode 100644 drivers/gpu/drm/xen/xen_drm_front_drv.h
 create mode 100644 drivers/gpu/drm/xen/xen_drm_front_kms.c
 create mode 100644 drivers/gpu/drm/xen/xen_drm_front_kms.h
diff mbox

Patch

diff --git a/drivers/gpu/drm/xen/Makefile b/drivers/gpu/drm/xen/Makefile
index 6c8c751f12ed..a7858693baae 100644
--- a/drivers/gpu/drm/xen/Makefile
+++ b/drivers/gpu/drm/xen/Makefile
@@ -1,6 +1,9 @@ 
 # SPDX-License-Identifier: GPL-2.0 OR MIT
 
 drm_xen_front-objs := xen_drm_front.o \
+		      xen_drm_front_drv.o \
+		      xen_drm_front_kms.o \
+		      xen_drm_front_conn.o \
 		      xen_drm_front_evtchnl.o \
 		      xen_drm_front_shbuf.o \
 		      xen_drm_front_cfg.o
diff --git a/drivers/gpu/drm/xen/xen_drm_front.c b/drivers/gpu/drm/xen/xen_drm_front.c
index fae631067c31..4e5059a280ba 100644
--- a/drivers/gpu/drm/xen/xen_drm_front.c
+++ b/drivers/gpu/drm/xen/xen_drm_front.c
@@ -10,6 +10,8 @@ 
 
 #include <drm/drmP.h>
 
+#include <linux/of_device.h>
+
 #include <xen/platform_pci.h>
 #include <xen/xen.h>
 #include <xen/xenbus.h>
@@ -17,11 +19,149 @@ 
 #include <xen/interface/io/displif.h>
 
 #include "xen_drm_front.h"
+#include "xen_drm_front_drv.h"
 #include "xen_drm_front_evtchnl.h"
 #include "xen_drm_front_shbuf.h"
 
+int xen_drm_front_mode_set(struct xen_drm_front_drm_pipeline *pipeline,
+		uint32_t x, uint32_t y, uint32_t width, uint32_t height,
+		uint32_t bpp, uint64_t fb_cookie)
+{
+	return 0;
+}
+
+static int be_dbuf_create_int(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie, uint32_t width, uint32_t height,
+		uint32_t bpp, uint64_t size, struct page **pages,
+		struct sg_table *sgt)
+{
+	return 0;
+}
+
+int xen_drm_front_dbuf_create_from_sgt(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie, uint32_t width, uint32_t height,
+		uint32_t bpp, uint64_t size, struct sg_table *sgt)
+{
+	return be_dbuf_create_int(front_info, dbuf_cookie, width, height,
+			bpp, size, NULL, sgt);
+}
+
+int xen_drm_front_dbuf_create_from_pages(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie, uint32_t width, uint32_t height,
+		uint32_t bpp, uint64_t size, struct page **pages)
+{
+	return be_dbuf_create_int(front_info, dbuf_cookie, width, height,
+			bpp, size, pages, NULL);
+}
+
+int xen_drm_front_dbuf_destroy(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie)
+{
+	return 0;
+}
+
+int xen_drm_front_fb_attach(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie, uint64_t fb_cookie, uint32_t width,
+		uint32_t height, uint32_t pixel_format)
+{
+	return 0;
+}
+
+int xen_drm_front_fb_detach(struct xen_drm_front_info *front_info,
+		uint64_t fb_cookie)
+{
+	return 0;
+}
+
+int xen_drm_front_page_flip(struct xen_drm_front_info *front_info,
+		int conn_idx, uint64_t fb_cookie)
+{
+	return 0;
+}
+
+void xen_drm_front_unload(struct xen_drm_front_info *front_info)
+{
+	if (front_info->xb_dev->state != XenbusStateReconfiguring)
+		return;
+
+	DRM_DEBUG("Can try removing driver now\n");
+	xenbus_switch_state(front_info->xb_dev, XenbusStateInitialising);
+}
+
+static int xen_drm_drv_probe(struct platform_device *pdev)
+{
+	/*
+	 * The device is not spawn from a device tree, so arch_setup_dma_ops
+	 * is not called, thus leaving the device with dummy DMA ops.
+	 * This makes the device return error on PRIME buffer import, which
+	 * is not correct: to fix this call of_dma_configure() with a NULL
+	 * node to set default DMA ops.
+	 */
+	of_dma_configure(&pdev->dev, NULL);
+	return xen_drm_front_drv_probe(pdev);
+}
+
+static int xen_drm_drv_remove(struct platform_device *pdev)
+{
+	return xen_drm_front_drv_remove(pdev);
+}
+
+struct platform_device_info xen_drm_front_platform_info = {
+	.name = XENDISPL_DRIVER_NAME,
+	.id = 0,
+	.num_res = 0,
+	.dma_mask = DMA_BIT_MASK(32),
+};
+
+static struct platform_driver xen_drm_front_front_info = {
+	.probe		= xen_drm_drv_probe,
+	.remove		= xen_drm_drv_remove,
+	.driver		= {
+		.name	= XENDISPL_DRIVER_NAME,
+	},
+};
+
+static void xen_drm_drv_deinit(struct xen_drm_front_info *front_info)
+{
+	if (!front_info->drm_pdrv_registered)
+		return;
+
+	if (front_info->drm_pdev)
+		platform_device_unregister(front_info->drm_pdev);
+
+	platform_driver_unregister(&xen_drm_front_front_info);
+	front_info->drm_pdrv_registered = false;
+	front_info->drm_pdev = NULL;
+}
+
+static int xen_drm_drv_init(struct xen_drm_front_info *front_info)
+{
+	int ret;
+
+	ret = platform_driver_register(&xen_drm_front_front_info);
+	if (ret < 0)
+		return ret;
+
+	front_info->drm_pdrv_registered = true;
+	/* pass card configuration via platform data */
+	xen_drm_front_platform_info.data = &front_info->cfg;
+	xen_drm_front_platform_info.size_data = sizeof(front_info->cfg);
+
+	front_info->drm_pdev = platform_device_register_full(
+			&xen_drm_front_platform_info);
+	if (IS_ERR_OR_NULL(front_info->drm_pdev)) {
+		DRM_ERROR("Failed to register " XENDISPL_DRIVER_NAME " PV DRM driver\n");
+		front_info->drm_pdev = NULL;
+		xen_drm_drv_deinit(front_info);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
 static void xen_drv_remove_internal(struct xen_drm_front_info *front_info)
 {
+	xen_drm_drv_deinit(front_info);
 	xen_drm_front_evtchnl_free_all(front_info);
 }
 
@@ -47,13 +187,29 @@  static int displback_initwait(struct xen_drm_front_info *front_info)
 static int displback_connect(struct xen_drm_front_info *front_info)
 {
 	xen_drm_front_evtchnl_set_state(front_info, EVTCHNL_STATE_CONNECTED);
-	return 0;
+	return xen_drm_drv_init(front_info);
 }
 
 static void displback_disconnect(struct xen_drm_front_info *front_info)
 {
+	bool removed = true;
+
+	if (front_info->drm_pdev) {
+		if (xen_drm_front_drv_is_used(front_info->drm_pdev)) {
+			DRM_WARN("DRM driver still in use, deferring removal\n");
+			removed = false;
+		} else
+			xen_drv_remove_internal(front_info);
+	}
+
 	xen_drm_front_evtchnl_set_state(front_info, EVTCHNL_STATE_DISCONNECTED);
-	xenbus_switch_state(front_info->xb_dev, XenbusStateInitialising);
+
+	if (removed)
+		xenbus_switch_state(front_info->xb_dev,
+				XenbusStateInitialising);
+	else
+		xenbus_switch_state(front_info->xb_dev,
+				XenbusStateReconfiguring);
 }
 
 static void displback_changed(struct xenbus_device *xb_dev,
@@ -136,6 +292,7 @@  static int xen_drv_probe(struct xenbus_device *xb_dev,
 
 	front_info->xb_dev = xb_dev;
 	spin_lock_init(&front_info->io_lock);
+	front_info->drm_pdrv_registered = false;
 	dev_set_drvdata(&xb_dev->dev, front_info);
 	return xenbus_switch_state(xb_dev, XenbusStateInitialising);
 }
diff --git a/drivers/gpu/drm/xen/xen_drm_front.h b/drivers/gpu/drm/xen/xen_drm_front.h
index 4adb6101c889..d964c4bd4fb6 100644
--- a/drivers/gpu/drm/xen/xen_drm_front.h
+++ b/drivers/gpu/drm/xen/xen_drm_front.h
@@ -11,6 +11,8 @@ 
 #ifndef __XEN_DRM_FRONT_H_
 #define __XEN_DRM_FRONT_H_
 
+#include <linux/scatterlist.h>
+
 #include "xen_drm_front_cfg.h"
 
 #ifndef GRANT_INVALID_REF
@@ -22,10 +24,13 @@ 
 #define GRANT_INVALID_REF	0
 #endif
 
+struct xen_drm_front_drm_pipeline;
+
 struct xen_drm_front_info {
 	struct xenbus_device *xb_dev;
 	/* to protect data between backend IO code and interrupt handler */
 	spinlock_t io_lock;
+	bool drm_pdrv_registered;
 	/* virtual DRM platform device */
 	struct platform_device *drm_pdev;
 
@@ -34,4 +39,31 @@  struct xen_drm_front_info {
 	struct xen_drm_front_cfg cfg;
 };
 
+int xen_drm_front_mode_set(struct xen_drm_front_drm_pipeline *pipeline,
+		uint32_t x, uint32_t y, uint32_t width, uint32_t height,
+		uint32_t bpp, uint64_t fb_cookie);
+
+int xen_drm_front_dbuf_create_from_sgt(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie, uint32_t width, uint32_t height,
+		uint32_t bpp, uint64_t size, struct sg_table *sgt);
+
+int xen_drm_front_dbuf_create_from_pages(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie, uint32_t width, uint32_t height,
+		uint32_t bpp, uint64_t size, struct page **pages);
+
+int xen_drm_front_dbuf_destroy(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie);
+
+int xen_drm_front_fb_attach(struct xen_drm_front_info *front_info,
+		uint64_t dbuf_cookie, uint64_t fb_cookie, uint32_t width,
+		uint32_t height, uint32_t pixel_format);
+
+int xen_drm_front_fb_detach(struct xen_drm_front_info *front_info,
+		uint64_t fb_cookie);
+
+int xen_drm_front_page_flip(struct xen_drm_front_info *front_info,
+		int conn_idx, uint64_t fb_cookie);
+
+void xen_drm_front_unload(struct xen_drm_front_info *front_info);
+
 #endif /* __XEN_DRM_FRONT_H_ */
diff --git a/drivers/gpu/drm/xen/xen_drm_front_conn.c b/drivers/gpu/drm/xen/xen_drm_front_conn.c
new file mode 100644
index 000000000000..382c8a9da7e6
--- /dev/null
+++ b/drivers/gpu/drm/xen/xen_drm_front_conn.c
@@ -0,0 +1,119 @@ 
+// 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 <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include <video/videomode.h>
+
+#include "xen_drm_front_conn.h"
+#include "xen_drm_front_drv.h"
+
+static struct xen_drm_front_drm_pipeline *
+to_xen_drm_pipeline(struct drm_connector *connector)
+{
+	return container_of(connector, struct xen_drm_front_drm_pipeline, conn);
+}
+
+static const uint32_t plane_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_RGB888,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB4444,
+	DRM_FORMAT_ARGB4444,
+	DRM_FORMAT_XRGB1555,
+	DRM_FORMAT_ARGB1555,
+};
+
+const uint32_t *xen_drm_front_conn_get_formats(int *format_count)
+{
+	*format_count = ARRAY_SIZE(plane_formats);
+	return plane_formats;
+}
+
+static int connector_detect(struct drm_connector *connector,
+		struct drm_modeset_acquire_ctx *ctx,
+		bool force)
+{
+	if (drm_dev_is_unplugged(connector->dev))
+		return connector_status_disconnected;
+
+	return connector_status_connected;
+}
+
+#define XEN_DRM_NUM_VIDEO_MODES		1
+#define XEN_DRM_CRTC_VREFRESH_HZ	60
+
+static int connector_get_modes(struct drm_connector *connector)
+{
+	struct xen_drm_front_drm_pipeline *pipeline =
+			to_xen_drm_pipeline(connector);
+	struct drm_display_mode *mode;
+	struct videomode videomode;
+	int width, height;
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode)
+		return 0;
+
+	memset(&videomode, 0, sizeof(videomode));
+	videomode.hactive = pipeline->width;
+	videomode.vactive = pipeline->height;
+	width = videomode.hactive + videomode.hfront_porch +
+			videomode.hback_porch + videomode.hsync_len;
+	height = videomode.vactive + videomode.vfront_porch +
+			videomode.vback_porch + videomode.vsync_len;
+	videomode.pixelclock = width * height * XEN_DRM_CRTC_VREFRESH_HZ;
+	mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
+
+	drm_display_mode_from_videomode(&videomode, mode);
+	drm_mode_probed_add(connector, mode);
+	return XEN_DRM_NUM_VIDEO_MODES;
+}
+
+static int connector_mode_valid(struct drm_connector *connector,
+		struct drm_display_mode *mode)
+{
+	struct xen_drm_front_drm_pipeline *pipeline =
+			to_xen_drm_pipeline(connector);
+
+	if (mode->hdisplay != pipeline->width)
+		return MODE_ERROR;
+
+	if (mode->vdisplay != pipeline->height)
+		return MODE_ERROR;
+
+	return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs connector_helper_funcs = {
+	.get_modes = connector_get_modes,
+	.mode_valid = connector_mode_valid,
+	.detect_ctx = connector_detect,
+};
+
+static const struct drm_connector_funcs connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+int xen_drm_front_conn_init(struct xen_drm_front_drm_info *drm_info,
+		struct drm_connector *connector)
+{
+	drm_connector_helper_add(connector, &connector_helper_funcs);
+
+	return drm_connector_init(drm_info->drm_dev, connector,
+		&connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
+}
diff --git a/drivers/gpu/drm/xen/xen_drm_front_conn.h b/drivers/gpu/drm/xen/xen_drm_front_conn.h
new file mode 100644
index 000000000000..f38c4b6db5df
--- /dev/null
+++ b/drivers/gpu/drm/xen/xen_drm_front_conn.h
@@ -0,0 +1,27 @@ 
+/* 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>
+ */
+
+#ifndef __XEN_DRM_FRONT_CONN_H_
+#define __XEN_DRM_FRONT_CONN_H_
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+
+#include <linux/wait.h>
+
+struct xen_drm_front_drm_info;
+
+int xen_drm_front_conn_init(struct xen_drm_front_drm_info *drm_info,
+		struct drm_connector *connector);
+
+const uint32_t *xen_drm_front_conn_get_formats(int *format_count);
+
+#endif /* __XEN_DRM_FRONT_CONN_H_ */
diff --git a/drivers/gpu/drm/xen/xen_drm_front_drv.c b/drivers/gpu/drm/xen/xen_drm_front_drv.c
new file mode 100644
index 000000000000..8887ac054601
--- /dev/null
+++ b/drivers/gpu/drm/xen/xen_drm_front_drv.c
@@ -0,0 +1,171 @@ 
+// 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 <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem.h>
+
+#include "xen_drm_front.h"
+#include "xen_drm_front_cfg.h"
+#include "xen_drm_front_drv.h"
+#include "xen_drm_front_kms.h"
+
+static int dumb_create(struct drm_file *filp, struct drm_device *dev,
+		struct drm_mode_create_dumb *args)
+{
+	return -EINVAL;
+}
+
+static void free_object(struct drm_gem_object *obj)
+{
+	struct xen_drm_front_drm_info *drm_info = obj->dev->dev_private;
+
+	xen_drm_front_dbuf_destroy(drm_info->front_info,
+			xen_drm_front_dbuf_to_cookie(obj));
+}
+
+void xen_drm_front_on_frame_done(struct platform_device *pdev,
+		int conn_idx, uint64_t fb_cookie)
+{
+	struct xen_drm_front_drm_info *drm_info = platform_get_drvdata(pdev);
+
+	if (unlikely(conn_idx >= drm_info->cfg->num_connectors))
+		return;
+
+	xen_drm_front_kms_on_frame_done(&drm_info->pipeline[conn_idx],
+			fb_cookie);
+}
+
+static void lastclose(struct drm_device *dev)
+{
+	struct xen_drm_front_drm_info *drm_info = dev->dev_private;
+
+	xen_drm_front_unload(drm_info->front_info);
+}
+
+static const struct file_operations xen_drm_fops = {
+	.owner          = THIS_MODULE,
+	.open           = drm_open,
+	.release        = drm_release,
+	.unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl   = drm_compat_ioctl,
+#endif
+	.poll           = drm_poll,
+	.read           = drm_read,
+	.llseek         = no_llseek,
+};
+
+static const struct vm_operations_struct xen_drm_vm_ops = {
+	.open           = drm_gem_vm_open,
+	.close          = drm_gem_vm_close,
+};
+
+struct drm_driver xen_drm_driver = {
+	.driver_features           = DRIVER_GEM | DRIVER_MODESET |
+				     DRIVER_PRIME | DRIVER_ATOMIC,
+	.lastclose                 = lastclose,
+	.gem_vm_ops                = &xen_drm_vm_ops,
+	.gem_free_object_unlocked  = free_object,
+	.prime_handle_to_fd        = drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle        = drm_gem_prime_fd_to_handle,
+	.gem_prime_import          = drm_gem_prime_import,
+	.gem_prime_export          = drm_gem_prime_export,
+	.dumb_create               = dumb_create,
+	.fops                      = &xen_drm_fops,
+	.name                      = "xendrm-du",
+	.desc                      = "Xen PV DRM Display Unit",
+	.date                      = "20180221",
+	.major                     = 1,
+	.minor                     = 0,
+};
+
+int xen_drm_front_drv_probe(struct platform_device *pdev)
+{
+	struct xen_drm_front_cfg *cfg = dev_get_platdata(&pdev->dev);
+	struct xen_drm_front_drm_info *drm_info;
+	struct drm_device *dev;
+	int ret;
+
+	DRM_INFO("Creating %s\n", xen_drm_driver.desc);
+
+	drm_info = devm_kzalloc(&pdev->dev, sizeof(*drm_info), GFP_KERNEL);
+	if (!drm_info)
+		return -ENOMEM;
+
+	drm_info->front_info = cfg->front_info;
+
+	dev = drm_dev_alloc(&xen_drm_driver, &pdev->dev);
+	if (!dev)
+		return -ENOMEM;
+
+	drm_info->drm_dev = dev;
+
+	drm_info->cfg = cfg;
+	dev->dev_private = drm_info;
+	platform_set_drvdata(pdev, drm_info);
+
+	ret = xen_drm_front_kms_init(drm_info);
+	if (ret) {
+		DRM_ERROR("Failed to initialize DRM/KMS, ret %d\n", ret);
+		goto fail_modeset;
+	}
+
+	ret = drm_dev_register(dev, 0);
+	if (ret)
+		goto fail_register;
+
+	DRM_INFO("Initialized %s %d.%d.%d %s on minor %d\n",
+			xen_drm_driver.name, xen_drm_driver.major,
+			xen_drm_driver.minor, xen_drm_driver.patchlevel,
+			xen_drm_driver.date, dev->primary->index);
+
+	return 0;
+
+fail_register:
+	drm_dev_unregister(dev);
+fail_modeset:
+	drm_mode_config_cleanup(dev);
+	return ret;
+}
+
+int xen_drm_front_drv_remove(struct platform_device *pdev)
+{
+	struct xen_drm_front_drm_info *drm_info = platform_get_drvdata(pdev);
+	struct drm_device *dev = drm_info->drm_dev;
+
+	if (dev) {
+		drm_dev_unregister(dev);
+		drm_atomic_helper_shutdown(dev);
+		drm_mode_config_cleanup(dev);
+		drm_dev_unref(dev);
+	}
+	return 0;
+}
+
+bool xen_drm_front_drv_is_used(struct platform_device *pdev)
+{
+	struct xen_drm_front_drm_info *drm_info = platform_get_drvdata(pdev);
+	struct drm_device *dev;
+
+	if (!drm_info)
+		return false;
+
+	dev = drm_info->drm_dev;
+	if (!dev)
+		return false;
+
+	/*
+	 * FIXME: the code below must be protected by drm_global_mutex,
+	 * but it is not accessible to us. Anyways there is a race condition,
+	 * but we will re-try.
+	 */
+	return dev->open_count != 0;
+}
diff --git a/drivers/gpu/drm/xen/xen_drm_front_drv.h b/drivers/gpu/drm/xen/xen_drm_front_drv.h
new file mode 100644
index 000000000000..cf3517b61979
--- /dev/null
+++ b/drivers/gpu/drm/xen/xen_drm_front_drv.h
@@ -0,0 +1,65 @@ 
+/* 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>
+ */
+
+#ifndef __XEN_DRM_FRONT_DRV_H_
+#define __XEN_DRM_FRONT_DRV_H_
+
+#include <drm/drmP.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "xen_drm_front.h"
+#include "xen_drm_front_cfg.h"
+#include "xen_drm_front_conn.h"
+
+struct xen_drm_front_drm_pipeline {
+	struct xen_drm_front_drm_info *drm_info;
+
+	int index;
+
+	struct drm_simple_display_pipe pipe;
+
+	struct drm_connector conn;
+	/* These are only for connector mode checking */
+	int width, height;
+
+	struct drm_pending_vblank_event *pending_event;
+};
+
+struct xen_drm_front_drm_info {
+	struct xen_drm_front_info *front_info;
+	struct drm_device *drm_dev;
+	struct xen_drm_front_cfg *cfg;
+
+	struct xen_drm_front_drm_pipeline pipeline[XEN_DRM_FRONT_MAX_CRTCS];
+};
+
+static inline uint64_t xen_drm_front_fb_to_cookie(
+		struct drm_framebuffer *fb)
+{
+	return (uint64_t)fb;
+}
+
+static inline uint64_t xen_drm_front_dbuf_to_cookie(
+		struct drm_gem_object *gem_obj)
+{
+	return (uint64_t)gem_obj;
+}
+
+int xen_drm_front_drv_probe(struct platform_device *pdev);
+
+int xen_drm_front_drv_remove(struct platform_device *pdev);
+
+bool xen_drm_front_drv_is_used(struct platform_device *pdev);
+
+void xen_drm_front_on_frame_done(struct platform_device *pdev,
+		int conn_idx, uint64_t fb_cookie);
+
+#endif /* __XEN_DRM_FRONT_DRV_H_ */
+
diff --git a/drivers/gpu/drm/xen/xen_drm_front_evtchnl.c b/drivers/gpu/drm/xen/xen_drm_front_evtchnl.c
index fd9289a6a1c8..228b9ffe8fd8 100644
--- a/drivers/gpu/drm/xen/xen_drm_front_evtchnl.c
+++ b/drivers/gpu/drm/xen/xen_drm_front_evtchnl.c
@@ -18,6 +18,7 @@ 
 #include <xen/grant_table.h>
 
 #include "xen_drm_front.h"
+#include "xen_drm_front_drv.h"
 #include "xen_drm_front_evtchnl.h"
 
 static irqreturn_t evtchnl_interrupt_ctrl(int irq, void *dev_id)
@@ -105,7 +106,8 @@  static irqreturn_t evtchnl_interrupt_evt(int irq, void *dev_id)
 
 		switch (event->type) {
 		case XENDISPL_EVT_PG_FLIP:
-			/* placeholder */
+			xen_drm_front_on_frame_done(front_info->drm_pdev,
+				evtchnl->index, event->op.pg_flip.fb_cookie);
 			break;
 		}
 	}
diff --git a/drivers/gpu/drm/xen/xen_drm_front_kms.c b/drivers/gpu/drm/xen/xen_drm_front_kms.c
new file mode 100644
index 000000000000..468995b6bf7a
--- /dev/null
+++ b/drivers/gpu/drm/xen/xen_drm_front_kms.c
@@ -0,0 +1,282 @@ 
+// 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_gem.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+
+#include "xen_drm_front.h"
+#include "xen_drm_front_conn.h"
+#include "xen_drm_front_drv.h"
+
+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;
+
+	xen_drm_front_fb_detach(drm_info->front_info,
+			xen_drm_front_fb_to_cookie(fb));
+	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_unreference_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,
+};
+
+void xen_drm_front_kms_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 xen_drm_front_drm_pipeline *pipeline =
+			to_xen_drm_pipeline(pipe);
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	int ret;
+
+	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);
+}
+
+static void display_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct xen_drm_front_drm_pipeline *pipeline =
+			to_xen_drm_pipeline(pipe);
+	int ret;
+
+	ret = xen_drm_front_mode_set(pipeline, 0, 0, 0, 0, 0,
+			xen_drm_front_fb_to_cookie(NULL));
+	if (ret)
+		DRM_ERROR("Failed to disable display: %d\n", ret);
+
+	/* release stalled event if any */
+	xen_drm_front_kms_send_pending_event(pipeline);
+}
+
+void xen_drm_front_kms_on_frame_done(
+		struct xen_drm_front_drm_pipeline *pipeline,
+		uint64_t fb_cookie)
+{
+	xen_drm_front_kms_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;
+
+		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);
+
+			/*
+			 * 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 int display_prepare_fb(struct drm_simple_display_pipe *pipe,
+		struct drm_plane_state *plane_state)
+{
+	return drm_gem_fb_prepare_fb(&pipe->plane, plane_state);
+}
+
+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;
+
+	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);
+
+	}
+	/*
+	 * 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))
+		xen_drm_front_kms_send_pending_event(pipeline);
+}
+
+static const struct drm_simple_display_pipe_funcs display_funcs = {
+	.enable = display_enable,
+	.disable = display_disable,
+	.prepare_fb = display_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 uint32_t *formats;
+	int format_count;
+	int ret;
+
+	pipeline->drm_info = drm_info;
+	pipeline->index = index;
+	pipeline->height = cfg->height;
+	pipeline->width = cfg->width;
+
+	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->cfg->num_connectors; i++) {
+		struct xen_drm_front_cfg_connector *cfg =
+				&drm_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);
+	return 0;
+}
diff --git a/drivers/gpu/drm/xen/xen_drm_front_kms.h b/drivers/gpu/drm/xen/xen_drm_front_kms.h
new file mode 100644
index 000000000000..74a2db3d687f
--- /dev/null
+++ b/drivers/gpu/drm/xen/xen_drm_front_kms.h
@@ -0,0 +1,22 @@ 
+/* 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>
+ */
+
+#ifndef __XEN_DRM_FRONT_KMS_H_
+#define __XEN_DRM_FRONT_KMS_H_
+
+#include "xen_drm_front_drv.h"
+
+int xen_drm_front_kms_init(struct xen_drm_front_drm_info *drm_info);
+
+void xen_drm_front_kms_on_frame_done(
+		struct xen_drm_front_drm_pipeline *pipeline,
+		uint64_t fb_cookie);
+
+#endif /* __XEN_DRM_FRONT_KMS_H_ */