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

Oleksandr Andrushchenko
2018-03-13T13:32:13Z

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 --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_ */
--
2.7.4