[PATCH] Initial backport of vkms changes from 6.4, including jshargo and brpols configs changes

From: Brandon Pollack
Date: Mon Aug 07 2023 - 22:44:02 EST


WIP: Need to run all tast criticals and test the multidisplay tests that
are WIP.

BUG=b:283357160
TEST=Booted on a betty-arc-r device and ran autologin.py -a
Change-Id: I13cef8cf019744813f51cfffed3d7ccb987834e8

Change-Id: Iae7d788bc4725dfdca044204fa1af27a5a1ec5a8
---
drivers/gpu/drm/vkms/Makefile | 1 +
drivers/gpu/drm/vkms/vkms_composer.c | 74 ++-
drivers/gpu/drm/vkms/vkms_configfs.c | 719 ++++++++++++++++++++++++++
drivers/gpu/drm/vkms/vkms_crtc.c | 98 ++--
drivers/gpu/drm/vkms/vkms_drv.c | 227 ++++----
drivers/gpu/drm/vkms/vkms_drv.h | 191 +++++--
drivers/gpu/drm/vkms/vkms_formats.c | 298 +++++------
drivers/gpu/drm/vkms/vkms_formats.h | 4 +-
drivers/gpu/drm/vkms/vkms_output.c | 351 +++++++++++--
drivers/gpu/drm/vkms/vkms_plane.c | 102 ++--
drivers/gpu/drm/vkms/vkms_writeback.c | 33 +-
include/drm/drm_fixed.h | 7 +
12 files changed, 1638 insertions(+), 467 deletions(-)
create mode 100644 drivers/gpu/drm/vkms/vkms_configfs.c

diff --git a/drivers/gpu/drm/vkms/Makefile b/drivers/gpu/drm/vkms/Makefile
index 1b28a6a32948..6b83907ad554 100644
--- a/drivers/gpu/drm/vkms/Makefile
+++ b/drivers/gpu/drm/vkms/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
vkms-y := \
+ vkms_configfs.o \
vkms_drv.o \
vkms_plane.o \
vkms_output.o \
diff --git a/drivers/gpu/drm/vkms/vkms_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c
index 8e53fa80742b..61061a277fca 100644
--- a/drivers/gpu/drm/vkms/vkms_composer.c
+++ b/drivers/gpu/drm/vkms/vkms_composer.c
@@ -4,6 +4,7 @@

#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_vblank.h>
@@ -22,7 +23,7 @@ static u16 pre_mul_blend_channel(u16 src, u16 dst, u16 alpha)

/**
* pre_mul_alpha_blend - alpha blending equation
- * @src_frame_info: source framebuffer's metadata
+ * @frame_info: Source framebuffer's metadata
* @stage_buffer: The line with the pixels from src_plane
* @output_buffer: A line buffer that receives all the blends output
*
@@ -53,10 +54,30 @@ static void pre_mul_alpha_blend(struct vkms_frame_info *frame_info,
}
}

-static bool check_y_limit(struct vkms_frame_info *frame_info, int y)
+static int get_y_pos(struct vkms_frame_info *frame_info, int y)
{
- if (y >= frame_info->dst.y1 && y < frame_info->dst.y2)
- return true;
+ if (frame_info->rotation & DRM_MODE_REFLECT_Y)
+ return drm_rect_height(&frame_info->rotated) - y - 1;
+
+ switch (frame_info->rotation & DRM_MODE_ROTATE_MASK) {
+ case DRM_MODE_ROTATE_90:
+ return frame_info->rotated.x2 - y - 1;
+ case DRM_MODE_ROTATE_270:
+ return y + frame_info->rotated.x1;
+ default:
+ return y;
+ }
+}
+
+static bool check_limit(struct vkms_frame_info *frame_info, int pos)
+{
+ if (drm_rotation_90_or_270(frame_info->rotation)) {
+ if (pos >= 0 && pos < drm_rect_width(&frame_info->rotated))
+ return true;
+ } else {
+ if (pos >= frame_info->rotated.y1 && pos < frame_info->rotated.y2)
+ return true;
+ }

return false;
}
@@ -69,11 +90,13 @@ static void fill_background(const struct pixel_argb_u16 *background_color,
}

/**
- * @wb_frame_info: The writeback frame buffer metadata
+ * blend - blend the pixels from all planes and compute crc
+ * @wb: The writeback frame buffer metadata
* @crtc_state: The crtc state
* @crc32: The crc output of the final frame
* @output_buffer: A buffer of a row that will receive the result of the blend(s)
* @stage_buffer: The line with the pixels from plane being blend to the output
+ * @row_size: The size, in bytes, of a single row
*
* This function blends the pixels (Using the `pre_mul_alpha_blend`)
* from all planes, calculates the crc32 of the output from the former step,
@@ -86,6 +109,7 @@ static void blend(struct vkms_writeback_job *wb,
{
struct vkms_plane_state **plane = crtc_state->active_planes;
u32 n_active_planes = crtc_state->num_active_planes;
+ int y_pos;

const struct pixel_argb_u16 background_color = { .a = 0xffff };

@@ -96,10 +120,12 @@ static void blend(struct vkms_writeback_job *wb,

/* The active planes are composed associatively in z-order. */
for (size_t i = 0; i < n_active_planes; i++) {
- if (!check_y_limit(plane[i]->frame_info, y))
+ y_pos = get_y_pos(plane[i]->frame_info, y);
+
+ if (!check_limit(plane[i]->frame_info, y_pos))
continue;

- plane[i]->plane_read(stage_buffer, plane[i]->frame_info, y);
+ vkms_compose_row(stage_buffer, plane[i], y_pos);
pre_mul_alpha_blend(plane[i]->frame_info, stage_buffer,
output_buffer);
}
@@ -107,7 +133,7 @@ static void blend(struct vkms_writeback_job *wb,
*crc32 = crc32_le(*crc32, (void *)output_buffer->pixels, row_size);

if (wb)
- wb->wb_write(&wb->wb_frame_info, output_buffer, y);
+ vkms_writeback_row(wb, output_buffer, y_pos);
}
}

@@ -118,10 +144,10 @@ static int check_format_funcs(struct vkms_crtc_state *crtc_state,
u32 n_active_planes = crtc_state->num_active_planes;

for (size_t i = 0; i < n_active_planes; i++)
- if (!planes[i]->plane_read)
+ if (!planes[i]->pixel_read)
return -1;

- if (active_wb && !active_wb->wb_write)
+ if (active_wb && !active_wb->pixel_write)
return -1;

return 0;
@@ -204,13 +230,13 @@ void vkms_composer_worker(struct work_struct *work)
composer_work);
struct drm_crtc *crtc = crtc_state->base.crtc;
struct vkms_writeback_job *active_wb = crtc_state->active_writeback;
- struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+ struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
bool crc_pending, wb_pending;
u64 frame_start, frame_end;
u32 crc32 = 0;
int ret;

- spin_lock_irq(&out->composer_lock);
+ spin_lock_irq(&vkms_crtc->composer_lock);
frame_start = crtc_state->frame_start;
frame_end = crtc_state->frame_end;
crc_pending = crtc_state->crc_pending;
@@ -218,7 +244,7 @@ void vkms_composer_worker(struct work_struct *work)
crtc_state->frame_start = 0;
crtc_state->frame_end = 0;
crtc_state->crc_pending = false;
- spin_unlock_irq(&out->composer_lock);
+ spin_unlock_irq(&vkms_crtc->composer_lock);

/*
* We raced with the vblank hrtimer and previous work already computed
@@ -236,10 +262,10 @@ void vkms_composer_worker(struct work_struct *work)
return;

if (wb_pending) {
- drm_writeback_signal_completion(&out->wb_connector, 0);
- spin_lock_irq(&out->composer_lock);
+ drm_writeback_signal_completion(&vkms_crtc->wb_connector, 0);
+ spin_lock_irq(&vkms_crtc->composer_lock);
crtc_state->wb_pending = false;
- spin_unlock_irq(&out->composer_lock);
+ spin_unlock_irq(&vkms_crtc->composer_lock);
}

/*
@@ -289,25 +315,25 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *src_name,
return 0;
}

-void vkms_set_composer(struct vkms_output *out, bool enabled)
+void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled)
{
bool old_enabled;

if (enabled)
- drm_crtc_vblank_get(&out->crtc);
+ drm_crtc_vblank_get(&vkms_crtc->base);

- spin_lock_irq(&out->lock);
- old_enabled = out->composer_enabled;
- out->composer_enabled = enabled;
- spin_unlock_irq(&out->lock);
+ spin_lock_irq(&vkms_crtc->lock);
+ old_enabled = vkms_crtc->composer_enabled;
+ vkms_crtc->composer_enabled = enabled;
+ spin_unlock_irq(&vkms_crtc->lock);

if (old_enabled)
- drm_crtc_vblank_put(&out->crtc);
+ drm_crtc_vblank_put(&vkms_crtc->base);
}

int vkms_set_crc_source(struct drm_crtc *crtc, const char *src_name)
{
- struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+ struct vkms_crtc *out = drm_crtc_to_vkms_crtc(crtc);
bool enabled = false;
int ret = 0;

diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
new file mode 100644
index 000000000000..a1ac7716ba1a
--- /dev/null
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -0,0 +1,719 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include "drm/drm_probe_helper.h"
+#include <linux/configfs.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "vkms_drv.h"
+
+/**
+ * DOC: ConfigFS Support for VKMS
+ *
+ * VKMS is instrumented with support for configuration via :doc:`ConfigFS
+ * <../filesystems/configfs>`.
+ *
+ * With VKMS installed, you can mount ConfigFS at ``/config/`` like so::
+ *
+ * mkdir -p /config/
+ * sudo mount -t configfs none /config
+ *
+ * This allows you to configure multiple virtual devices in addition to an
+ * immutable "default" device created by the driver at initialization time. Note
+ * that the default device is immutable because we cannot pre-populate ConfigFS
+ * directories with normal files.
+ *
+ * To set up a new device, create a new directory under the VKMS configfs
+ * directory::
+ *
+ * mkdir /config/vkms/test
+ *
+ * With your device created you'll find an new directory ready to be
+ * configured::
+ *
+ * /config
+ * `-- vkms
+ * |-- default
+ * | `-- enabled
+ * `-- test
+ * |-- connectors
+ * `-- connected
+ * |-- crtcs
+ * |-- encoders
+ * |-- planes
+ * `-- enabled
+ *
+ * Each directory you add within the connectors, crtcs, encoders, and planes
+ * directories will let you configure a new object of that type. Adding new
+ * objects will automatically create a set of default files and folders you can
+ * use to configure that object.
+ *
+ * For instance, we can set up a two-output device like so::
+ *
+ * DRM_PLANE_TYPE_PRIMARY=1
+ * DRM_PLANE_TYPE_CURSOR=2
+ * DRM_PLANE_TYPE_OVERLAY=0
+ *
+ * mkdir /config/vkms/test/planes/primary
+ * echo $DRM_PLANE_TYPE_PRIMARY > /config/vkms/test/planes/primary/type
+ *
+ * mkdir /config/vkms/test/planes/other_primary
+ * echo $DRM_PLANE_TYPE_PRIMARY > /config/vkms/test/planes/other_primary/type
+ *
+ * mkdir /config/vkms/test/planes/cursor
+ * echo $DRM_PLANE_TYPE_CURSOR > /config/vkms/test/planes/cursor/type
+ *
+ * mkdir /config/vkms/test/planes/overlay
+ * echo $DRM_PLANE_TYPE_OVERLAY > /config/vkms/test/planes/overlay/type
+ *
+ * mkdir /config/vkms/test/crtcs/crtc
+ * mkdir /config/vkms/test/crtcs/crtc_other
+ * mkdir /config/vkms/test/encoders/encoder
+ * mkdir /config/vkms/test/connectors/connector
+ *
+ * You can see that specific attributes, such as ``.../<plane>/type``, can be
+ * configured by writing into them. Associating objects together can be done via
+ * symlinks::
+ *
+ * ln -s /config/vkms/test/encoders/encoder /config/vkms/test/connectors/connector/possible_encoders
+ *
+ * ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/encoders/encoder/possible_crtcs/
+ * ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/primary/possible_crtcs/
+ * ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/cursor/possible_crtcs/
+ * ln -s /config/vkms/test/crtcs/crtc /config/vkms/test/planes/overlay/possible_crtcs/
+ *
+ * ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/overlay/possible_crtcs/
+ * ln -s /config/vkms/test/crtcs/crtc_other /config/vkms/test/planes/other_primary/possible_crtcs/
+ *
+ * Finally, to enable your configured device, just write 1 to the ``enabled``
+ * file::
+ *
+ * echo 1 > /config/vkms/test/enabled
+ *
+ * By default no display is "connected" so to connect a connector you'll also
+ * have to write 1 to a connectors "connected" attribute::
+ *
+ * echo 1 > /config/vkms/test/connectors/connector/connected
+ *
+ * One can verify that this is worked using the `modetest` utility or the
+ * equivalent for your platform.
+ *
+ * When you're done with the virtual device, you can clean up the device like
+ * so::
+ *
+ * echo 0 > /config/vkms/test/enabled
+ *
+ * rm /config/vkms/test/connectors/connector/possible_encoders/encoder
+ * rm /config/vkms/test/encoders/encoder/possible_crtcs/crtc
+ * rm /config/vkms/test/planes/primary/possible_crtcs/crtc
+ * rm /config/vkms/test/planes/cursor/possible_crtcs/crtc
+ * rm /config/vkms/test/planes/overlay/possible_crtcs/crtc
+ * rm /config/vkms/test/planes/overlay/possible_crtcs/crtc_other
+ * rm /config/vkms/test/planes/other_primary/possible_crtcs/crtc_other
+ *
+ * rmdir /config/vkms/test/planes/primary
+ * rmdir /config/vkms/test/planes/other_primary
+ * rmdir /config/vkms/test/planes/cursor
+ * rmdir /config/vkms/test/planes/overlay
+ * rmdir /config/vkms/test/crtcs/crtc
+ * rmdir /config/vkms/test/crtcs/crtc_other
+ * rmdir /config/vkms/test/encoders/encoder
+ * rmdir /config/vkms/test/connectors/connector
+ *
+ * rmdir /config/vkms/test
+ */
+
+/*
+ * Common helpers (i.e. common sub-groups)
+ */
+
+/* Possible CRTCs, e.g. /config/vkms/device/<object>/possible_crtcs/<symlink> */
+
+static struct config_item_type crtc_type;
+
+static int possible_crtcs_allow_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_config_links *links = item_to_config_links(src);
+ struct vkms_config_crtc *crtc;
+
+ if (target->ci_type != &crtc_type) {
+ DRM_ERROR("Unable to link non-CRTCs.\n");
+ return -EINVAL;
+ }
+
+ crtc = item_to_config_crtc(target);
+
+ if (links->linked_object_bitmap & BIT(crtc->crtc_config_idx)) {
+ DRM_ERROR(
+ "Tried to add two symlinks to the same CRTC from the same object\n");
+ return -EINVAL;
+ }
+
+ links->linked_object_bitmap |= BIT(crtc->crtc_config_idx);
+
+ return 0;
+}
+
+static void possible_crtcs_drop_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_config_links *links = item_to_config_links(src);
+ struct vkms_config_crtc *crtc = item_to_config_crtc(target);
+
+ links->linked_object_bitmap &= ~BIT(crtc->crtc_config_idx);
+}
+
+static struct configfs_item_operations possible_crtcs_item_ops = {
+ .allow_link = &possible_crtcs_allow_link,
+ .drop_link = &possible_crtcs_drop_link,
+};
+
+static struct config_item_type possible_crtcs_group_type = {
+ .ct_item_ops = &possible_crtcs_item_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static void add_possible_crtcs(struct config_group *parent,
+ struct config_group *possible_crtcs)
+{
+ config_group_init_type_name(possible_crtcs, "possible_crtcs",
+ &possible_crtcs_group_type);
+ configfs_add_default_group(possible_crtcs, parent);
+}
+
+/* Possible encoders, e.g. /config/vkms/device/connector/possible_encoders/<symlink> */
+
+static struct config_item_type encoder_type;
+
+static int possible_encoders_allow_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_config_links *links = item_to_config_links(src);
+ struct vkms_config_encoder *encoder;
+
+ if (target->ci_type != &encoder_type) {
+ DRM_ERROR("Unable to link non-encoders.\n");
+ return -EINVAL;
+ }
+
+ encoder = item_to_config_encoder(target);
+
+ if (links->linked_object_bitmap & BIT(encoder->encoder_config_idx)) {
+ DRM_ERROR(
+ "Tried to add two symlinks to the same encoder from the same object\n");
+ return -EINVAL;
+ }
+
+ links->linked_object_bitmap |= BIT(encoder->encoder_config_idx);
+
+ return 0;
+}
+
+static void possible_encoders_drop_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_config_links *links = item_to_config_links(src);
+ struct vkms_config_encoder *encoder = item_to_config_encoder(target);
+
+ links->linked_object_bitmap &= ~BIT(encoder->encoder_config_idx);
+}
+
+static struct configfs_item_operations possible_encoders_item_ops = {
+ .allow_link = &possible_encoders_allow_link,
+ .drop_link = &possible_encoders_drop_link,
+};
+
+static struct config_item_type possible_encoders_group_type = {
+ .ct_item_ops = &possible_encoders_item_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static void add_possible_encoders(struct config_group *parent,
+ struct config_group *possible_encoders)
+{
+ config_group_init_type_name(possible_encoders, "possible_encoders",
+ &possible_encoders_group_type);
+ configfs_add_default_group(possible_encoders, parent);
+}
+
+/*
+ * Individual objects (connectors, crtcs, encoders, planes):
+ */
+
+/* Connector item, e.g. /config/vkms/device/connectors/ID */
+
+static ssize_t connector_connected_show(struct config_item *item, char *buf)
+{
+ struct vkms_config_connector *connector =
+ item_to_config_connector(item);
+ struct vkms_configfs *configfs = connector_item_to_configfs(item);
+ bool connected = false;
+
+ mutex_lock(&configfs->lock);
+ connected = connector->connected;
+ mutex_unlock(&configfs->lock);
+
+ return sprintf(buf, "%d\n", connected);
+}
+
+static ssize_t connector_connected_store(struct config_item *item,
+ const char *buf, size_t len)
+{
+ struct vkms_config_connector *connector =
+ item_to_config_connector(item);
+ struct vkms_configfs *configfs = connector_item_to_configfs(item);
+ int val, ret;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val != 1 && val != 0)
+ return -EINVAL;
+
+ mutex_lock(&configfs->lock);
+ connector->connected = val;
+ if (!connector->connector) {
+ pr_info("VKMS Device %s is not yet enabled, connector will be enabled on start",
+ configfs->device_group.cg_item.ci_name);
+ }
+ mutex_unlock(&configfs->lock);
+
+ if (connector->connector)
+ drm_kms_helper_hotplug_event(connector->connector->dev);
+
+ return len;
+}
+
+CONFIGFS_ATTR(connector_, connected);
+
+static struct configfs_attribute *connector_attrs[] = {
+ &connector_attr_connected,
+ NULL,
+};
+
+static struct config_item_type connector_type = {
+ .ct_attrs = connector_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+/* Crtc item, e.g. /config/vkms/device/crtcs/ID */
+
+static struct config_item_type crtc_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+/* Encoder item, e.g. /config/vkms/device/encoder/ID */
+
+static struct config_item_type encoder_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+/* Plane item, e.g. /config/vkms/device/planes/ID */
+
+static ssize_t plane_type_show(struct config_item *item, char *buf)
+{
+ struct vkms_config_plane *plane = item_to_config_plane(item);
+ struct vkms_configfs *configfs = plane_item_to_configfs(item);
+ enum drm_plane_type plane_type;
+
+ mutex_lock(&configfs->lock);
+ plane_type = plane->type;
+ mutex_unlock(&configfs->lock);
+
+ return sprintf(buf, "%u\n", plane_type);
+}
+
+static ssize_t plane_type_store(struct config_item *item, const char *buf,
+ size_t len)
+{
+ struct vkms_config_plane *plane = item_to_config_plane(item);
+ struct vkms_configfs *configfs = plane_item_to_configfs(item);
+ int val, ret;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val != DRM_PLANE_TYPE_PRIMARY && val != DRM_PLANE_TYPE_CURSOR &&
+ val != DRM_PLANE_TYPE_OVERLAY)
+ return -EINVAL;
+
+ mutex_lock(&configfs->lock);
+ plane->type = val;
+ mutex_unlock(&configfs->lock);
+
+ return len;
+}
+
+CONFIGFS_ATTR(plane_, type);
+
+static struct configfs_attribute *plane_attrs[] = {
+ &plane_attr_type,
+ NULL,
+};
+
+static struct config_item_type plane_type = {
+ .ct_attrs = plane_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+/*
+ * Directory groups, e.g. /config/vkms/device/{planes, crtcs, ...}
+ */
+
+/* Connectors group: /config/vkms/device/connectors/ */
+
+static struct config_group *connectors_group_make(struct config_group *group,
+ const char *name)
+{
+ struct vkms_config_connector *connector =
+ kzalloc(sizeof(*connector), GFP_KERNEL);
+ if (!connector)
+ return ERR_PTR(-ENOMEM);
+
+ config_group_init_type_name(&connector->config_group, name,
+ &connector_type);
+ add_possible_encoders(&connector->config_group,
+ &connector->possible_encoders.group);
+ connector->connected = false;
+
+ return &connector->config_group;
+}
+
+static void connectors_group_drop(struct config_group *group,
+ struct config_item *item)
+{
+ struct vkms_config_connector *connector =
+ item_to_config_connector(item);
+ kfree(connector);
+}
+
+static struct configfs_group_operations connectors_group_ops = {
+ .make_group = &connectors_group_make,
+ .drop_item = &connectors_group_drop,
+};
+
+static struct config_item_type connectors_group_type = {
+ .ct_group_ops = &connectors_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/* CRTCs group: /config/vkms/device/crtcs/ */
+
+static struct config_group *crtcs_group_make(struct config_group *group,
+ const char *name)
+{
+ struct vkms_configfs *configfs =
+ container_of(group, struct vkms_configfs, crtcs_group);
+ unsigned long next_idx;
+ struct vkms_config_crtc *crtc;
+
+ mutex_lock(&configfs->lock);
+
+ next_idx = find_first_zero_bit(&configfs->allocated_crtcs,
+ VKMS_MAX_OUTPUT_OBJECTS);
+
+ if (next_idx == VKMS_MAX_OUTPUT_OBJECTS) {
+ DRM_ERROR("Unable to allocate another CRTC.\n");
+ mutex_unlock(&configfs->lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ crtc = kzalloc(sizeof(*crtc), GFP_KERNEL);
+ if (!crtc) {
+ DRM_ERROR("Unable to allocate CRTC.\n");
+ mutex_unlock(&configfs->lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ config_group_init_type_name(&crtc->config_group, name, &crtc_type);
+ crtc->crtc_config_idx = next_idx;
+
+ set_bit(next_idx, &configfs->allocated_crtcs);
+
+ mutex_unlock(&configfs->lock);
+
+ return &crtc->config_group;
+}
+
+static void crtcs_group_drop(struct config_group *group,
+ struct config_item *item)
+{
+ struct vkms_config_crtc *crtc = item_to_config_crtc(item);
+ kfree(crtc);
+}
+
+static struct configfs_group_operations crtcs_group_ops = {
+ .make_group = &crtcs_group_make,
+ .drop_item = &crtcs_group_drop,
+};
+
+static struct config_item_type crtcs_group_type = {
+ .ct_group_ops = &crtcs_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/* Encoders group: /config/vkms/device/encoders/ */
+
+static struct config_group *encoders_group_make(struct config_group *group,
+ const char *name)
+{
+ struct vkms_configfs *configfs =
+ container_of(group, struct vkms_configfs, encoders_group);
+ unsigned long next_idx;
+ struct vkms_config_encoder *encoder;
+
+ mutex_lock(&configfs->lock);
+
+ next_idx = find_first_zero_bit(&configfs->allocated_encoders,
+ VKMS_MAX_OUTPUT_OBJECTS);
+
+ if (next_idx == VKMS_MAX_OUTPUT_OBJECTS) {
+ DRM_ERROR("Unable to allocate another encoder.\n");
+ mutex_unlock(&configfs->lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ encoder = kzalloc(sizeof(*encoder), GFP_KERNEL);
+ if (!encoder) {
+ DRM_ERROR("Unable to allocate encoder.\n");
+ mutex_unlock(&configfs->lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ config_group_init_type_name(&encoder->config_group, name,
+ &encoder_type);
+ add_possible_crtcs(&encoder->config_group,
+ &encoder->possible_crtcs.group);
+ encoder->encoder_config_idx = next_idx;
+ set_bit(next_idx, &configfs->allocated_encoders);
+
+ mutex_unlock(&configfs->lock);
+
+ return &encoder->config_group;
+}
+
+static void encoders_group_drop(struct config_group *group,
+ struct config_item *item)
+{
+ struct vkms_config_encoder *encoder = item_to_config_encoder(item);
+ kfree(encoder);
+}
+
+static struct configfs_group_operations encoders_group_ops = {
+ .make_group = &encoders_group_make,
+ .drop_item = &encoders_group_drop,
+};
+
+static struct config_item_type encoders_group_type = {
+ .ct_group_ops = &encoders_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/* Planes group: /config/vkms/device/planes/ */
+
+static struct config_group *make_plane_group(struct config_group *group,
+ const char *name)
+{
+ struct vkms_config_plane *plane = kzalloc(sizeof(*plane), GFP_KERNEL);
+ if (!plane)
+ return ERR_PTR(-ENOMEM);
+
+ config_group_init_type_name(&plane->config_group, name, &plane_type);
+ add_possible_crtcs(&plane->config_group, &plane->possible_crtcs.group);
+
+ return &plane->config_group;
+}
+
+static void drop_plane_group(struct config_group *group,
+ struct config_item *item)
+{
+ struct vkms_config_plane *plane = item_to_config_plane(item);
+ kfree(plane);
+}
+
+static struct configfs_group_operations plane_group_ops = {
+ .make_group = &make_plane_group,
+ .drop_item = &drop_plane_group,
+};
+
+static struct config_item_type planes_group_type = {
+ .ct_group_ops = &plane_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/* Root directory group, e.g. /config/vkms/device */
+
+static ssize_t device_enabled_show(struct config_item *item, char *buf)
+{
+ struct vkms_configfs *configfs = item_to_configfs(item);
+ bool is_enabled;
+
+ mutex_lock(&configfs->lock);
+ is_enabled = configfs->vkms_device != NULL;
+ mutex_unlock(&configfs->lock);
+
+ return sprintf(buf, "%d\n", is_enabled);
+}
+
+static ssize_t device_enabled_store(struct config_item *item, const char *buf,
+ size_t len)
+{
+ struct vkms_configfs *configfs = item_to_configfs(item);
+ struct vkms_device *device;
+ int enabled, ret;
+
+ ret = kstrtoint(buf, 0, &enabled);
+ if (ret)
+ return ret;
+
+ if (enabled == 0) {
+ mutex_lock(&configfs->lock);
+ if (configfs->vkms_device) {
+ vkms_remove_device(configfs->vkms_device);
+ configfs->vkms_device = NULL;
+ }
+ mutex_unlock(&configfs->lock);
+
+ return len;
+ }
+
+ if (enabled == 1) {
+ mutex_lock(&configfs->lock);
+ if (!configfs->vkms_device) {
+ device = vkms_add_device(configfs);
+ if (IS_ERR(device)) {
+ mutex_unlock(&configfs->lock);
+ return -PTR_ERR(device);
+ }
+
+ configfs->vkms_device = device;
+ }
+ mutex_unlock(&configfs->lock);
+
+ return len;
+ }
+
+ return -EINVAL;
+}
+
+CONFIGFS_ATTR(device_, enabled);
+
+static ssize_t device_id_show(struct config_item *item, char *buf)
+{
+ struct vkms_configfs *configfs = item_to_configfs(item);
+ int id = -1;
+
+ mutex_lock(&configfs->lock);
+ if (configfs->vkms_device) {
+ id = configfs->vkms_device->platform->id;
+ }
+ mutex_unlock(&configfs->lock);
+
+ return sprintf(buf, "%d\n", id);
+}
+
+CONFIGFS_ATTR_RO(device_, id);
+
+static struct configfs_attribute *device_group_attrs[] = {
+ &device_attr_id,
+ &device_attr_enabled,
+ NULL,
+};
+
+static struct config_item_type device_group_type = {
+ .ct_attrs = device_group_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static void vkms_configfs_setup_default_groups(struct vkms_configfs *configfs,
+ const char *name)
+{
+ config_group_init_type_name(&configfs->device_group, name,
+ &device_group_type);
+
+ config_group_init_type_name(&configfs->connectors_group, "connectors",
+ &connectors_group_type);
+ configfs_add_default_group(&configfs->connectors_group,
+ &configfs->device_group);
+
+ config_group_init_type_name(&configfs->crtcs_group, "crtcs",
+ &crtcs_group_type);
+ configfs_add_default_group(&configfs->crtcs_group,
+ &configfs->device_group);
+
+ config_group_init_type_name(&configfs->encoders_group, "encoders",
+ &encoders_group_type);
+ configfs_add_default_group(&configfs->encoders_group,
+ &configfs->device_group);
+
+ config_group_init_type_name(&configfs->planes_group, "planes",
+ &planes_group_type);
+ configfs_add_default_group(&configfs->planes_group,
+ &configfs->device_group);
+}
+
+/* Root directory group and subsystem, e.g. /config/vkms/ */
+
+static struct config_group *make_root_group(struct config_group *group,
+ const char *name)
+{
+ struct vkms_configfs *configfs = kzalloc(sizeof(*configfs), GFP_KERNEL);
+
+ if (!configfs)
+ return ERR_PTR(-ENOMEM);
+
+ vkms_configfs_setup_default_groups(configfs, name);
+ mutex_init(&configfs->lock);
+
+ return &configfs->device_group;
+}
+
+static void drop_root_group(struct config_group *group,
+ struct config_item *item)
+{
+ struct vkms_configfs *configfs = item_to_configfs(item);
+
+ mutex_lock(&configfs->lock);
+ if (configfs->vkms_device)
+ vkms_remove_device(configfs->vkms_device);
+ mutex_unlock(&configfs->lock);
+
+ kfree(configfs);
+}
+
+static struct configfs_group_operations root_group_ops = {
+ .make_group = &make_root_group,
+ .drop_item = &drop_root_group,
+};
+
+static struct config_item_type vkms_type = {
+ .ct_group_ops = &root_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem vkms_subsys = {
+ .su_group = {
+ .cg_item = {
+ .ci_name = "vkms",
+ .ci_type = &vkms_type,
+ },
+ },
+ .su_mutex = __MUTEX_INITIALIZER(vkms_subsys.su_mutex),
+};
+
+int vkms_init_configfs(void)
+{
+ config_group_init(&vkms_subsys.su_group);
+ return configfs_register_subsystem(&vkms_subsys);
+}
+
+void vkms_unregister_configfs(void)
+{
+ configfs_unregister_subsystem(&vkms_subsys);
+}
diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
index 57bbd32e9beb..5ebb5264f6ef 100644
--- a/drivers/gpu/drm/vkms/vkms_crtc.c
+++ b/drivers/gpu/drm/vkms/vkms_crtc.c
@@ -11,35 +11,34 @@

static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
{
- struct vkms_output *output = container_of(timer, struct vkms_output,
- vblank_hrtimer);
- struct drm_crtc *crtc = &output->crtc;
+ struct vkms_crtc *vkms_crtc = timer_to_vkms_crtc(timer);
+ struct drm_crtc *crtc = &vkms_crtc->base;
struct vkms_crtc_state *state;
u64 ret_overrun;
bool ret, fence_cookie;

fence_cookie = dma_fence_begin_signalling();

- ret_overrun = hrtimer_forward_now(&output->vblank_hrtimer,
- output->period_ns);
+ ret_overrun = hrtimer_forward_now(&vkms_crtc->vblank_hrtimer,
+ vkms_crtc->period_ns);
if (ret_overrun != 1)
pr_warn("%s: vblank timer overrun\n", __func__);

- spin_lock(&output->lock);
+ spin_lock(&vkms_crtc->lock);
ret = drm_crtc_handle_vblank(crtc);
if (!ret)
DRM_ERROR("vkms failure on handling vblank");

- state = output->composer_state;
- spin_unlock(&output->lock);
+ state = vkms_crtc->composer_state;
+ spin_unlock(&vkms_crtc->lock);

- if (state && output->composer_enabled) {
+ if (state && vkms_crtc->composer_enabled) {
u64 frame = drm_crtc_accurate_vblank_count(crtc);

/* update frame_start only if a queued vkms_composer_worker()
* has read the data
*/
- spin_lock(&output->composer_lock);
+ spin_lock(&vkms_crtc->composer_lock);
if (!state->crc_pending)
state->frame_start = frame;
else
@@ -47,9 +46,10 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
state->frame_start, frame);
state->frame_end = frame;
state->crc_pending = true;
- spin_unlock(&output->composer_lock);
+ spin_unlock(&vkms_crtc->composer_lock);

- ret = queue_work(output->composer_workq, &state->composer_work);
+ ret = queue_work(vkms_crtc->composer_workq,
+ &state->composer_work);
if (!ret)
DRM_DEBUG_DRIVER("Composer worker already queued\n");
}
@@ -62,25 +62,27 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
static int vkms_enable_vblank(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
+ struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
unsigned int pipe = drm_crtc_index(crtc);
struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
- struct vkms_output *out = drm_crtc_to_vkms_output(crtc);

drm_calc_timestamping_constants(crtc, &crtc->mode);

- hrtimer_init(&out->vblank_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
- out->vblank_hrtimer.function = &vkms_vblank_simulate;
- out->period_ns = ktime_set(0, vblank->framedur_ns);
- hrtimer_start(&out->vblank_hrtimer, out->period_ns, HRTIMER_MODE_REL);
+ hrtimer_init(&vkms_crtc->vblank_hrtimer, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+ vkms_crtc->vblank_hrtimer.function = &vkms_vblank_simulate;
+ vkms_crtc->period_ns = ktime_set(0, vblank->framedur_ns);
+ hrtimer_start(&vkms_crtc->vblank_hrtimer, vkms_crtc->period_ns,
+ HRTIMER_MODE_REL);

return 0;
}

static void vkms_disable_vblank(struct drm_crtc *crtc)
{
- struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
+ struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);

- hrtimer_cancel(&out->vblank_hrtimer);
+ hrtimer_cancel(&vkms_crtc->vblank_hrtimer);
}

static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
@@ -88,9 +90,8 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
bool in_vblank_irq)
{
struct drm_device *dev = crtc->dev;
+ struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);
unsigned int pipe = crtc->index;
- struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
- struct vkms_output *output = &vkmsdev->output;
struct drm_vblank_crtc *vblank = &dev->vblank[pipe];

if (!READ_ONCE(vblank->enabled)) {
@@ -98,7 +99,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
return true;
}

- *vblank_time = READ_ONCE(output->vblank_hrtimer.node.expires);
+ *vblank_time = READ_ONCE(vkms_crtc->vblank_hrtimer.node.expires);

if (WARN_ON(*vblank_time == vblank->time))
return true;
@@ -110,7 +111,7 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
* the vblank core expects. Therefore we need to always correct the
* timestampe by one frame.
*/
- *vblank_time -= output->period_ns;
+ *vblank_time -= vkms_crtc->period_ns;

return true;
}
@@ -161,7 +162,6 @@ static void vkms_atomic_crtc_reset(struct drm_crtc *crtc)

static const struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
- .destroy = drm_crtc_cleanup,
.page_flip = drm_atomic_helper_page_flip,
.reset = vkms_atomic_crtc_reset,
.atomic_duplicate_state = vkms_atomic_crtc_duplicate_state,
@@ -237,18 +237,18 @@ static void vkms_crtc_atomic_disable(struct drm_crtc *crtc,
static void vkms_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
- struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
+ struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);

/* This lock is held across the atomic commit to block vblank timer
* from scheduling vkms_composer_worker until the composer is updated
*/
- spin_lock_irq(&vkms_output->lock);
+ spin_lock_irq(&vkms_crtc->lock);
}

static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
- struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
+ struct vkms_crtc *vkms_crtc = drm_crtc_to_vkms_crtc(crtc);

if (crtc->state->event) {
spin_lock(&crtc->dev->event_lock);
@@ -263,9 +263,9 @@ static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
crtc->state->event = NULL;
}

- vkms_output->composer_state = to_vkms_crtc_state(crtc->state);
+ vkms_crtc->composer_state = to_vkms_crtc_state(crtc->state);

- spin_unlock_irq(&vkms_output->lock);
+ spin_unlock_irq(&vkms_crtc->lock);
}

static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
@@ -276,27 +276,41 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
.atomic_disable = vkms_crtc_atomic_disable,
};

-int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
- struct drm_plane *primary, struct drm_plane *cursor)
+struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
+ struct drm_plane *primary,
+ struct drm_plane *cursor, const char *name)
{
- struct vkms_output *vkms_out = drm_crtc_to_vkms_output(crtc);
+ struct drm_device *dev = &vkmsdev->drm;
+ struct vkms_crtc *vkms_crtc;
int ret;

- ret = drm_crtc_init_with_planes(dev, crtc, primary, cursor,
- &vkms_crtc_funcs, NULL);
+ if (vkmsdev->output.num_crtcs >= VKMS_MAX_OUTPUT_OBJECTS) {
+ return ERR_PTR(-ENOMEM);
+ }
+ vkms_crtc = &vkmsdev->output.crtcs[vkmsdev->output.num_crtcs++];
+
+ ret = drmm_crtc_init_with_planes(dev, &vkms_crtc->base, primary, cursor,
+ &vkms_crtc_funcs, name);
if (ret) {
DRM_ERROR("Failed to init CRTC\n");
- return ret;
+ goto out_error;
}

- drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs);
+ drm_crtc_helper_add(&vkms_crtc->base, &vkms_crtc_helper_funcs);

- spin_lock_init(&vkms_out->lock);
- spin_lock_init(&vkms_out->composer_lock);
+ spin_lock_init(&vkms_crtc->lock);
+ spin_lock_init(&vkms_crtc->composer_lock);

- vkms_out->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
- if (!vkms_out->composer_workq)
- return -ENOMEM;
+ vkms_crtc->composer_workq = alloc_ordered_workqueue("vkms_composer", 0);
+ if (!vkms_crtc->composer_workq) {
+ ret = -ENOMEM;
+ goto out_error;
+ }
+
+ return vkms_crtc;

- return ret;
+out_error:
+ memset(vkms_crtc, 0, sizeof(*vkms_crtc));
+ vkmsdev->output.num_crtcs -= 1;
+ return ERR_PTR(ret);
}
diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
index 69346906ec81..7e1ec7d89dcb 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -9,10 +9,15 @@
* the GPU in DRM API tests.
*/

+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>

+#include <drm/drm_device.h>
#include <drm/drm_gem.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
@@ -37,19 +42,26 @@
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 0

-static struct vkms_config *default_config;
+static bool enable_default_device = true;
+module_param_named(enable_default_device, enable_default_device, bool, 0444);
+MODULE_PARM_DESC(enable_default_device,
+ "Enable/Disable creating the default device");

static bool enable_cursor = true;
module_param_named(enable_cursor, enable_cursor, bool, 0444);
-MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
+MODULE_PARM_DESC(enable_cursor,
+ "Enable/Disable cursor support for the default device");

static bool enable_writeback = true;
module_param_named(enable_writeback, enable_writeback, bool, 0444);
-MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback connector support");
+MODULE_PARM_DESC(
+ enable_writeback,
+ "Enable/Disable writeback connector support for the default device");

static bool enable_overlay;
module_param_named(enable_overlay, enable_overlay, bool, 0444);
-MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
+MODULE_PARM_DESC(enable_overlay,
+ "Enable/Disable overlay support for the default device");

DEFINE_DRM_GEM_FOPS(vkms_driver_fops);

@@ -57,8 +69,8 @@ static void vkms_release(struct drm_device *dev)
{
struct vkms_device *vkms = drm_device_to_vkms_device(dev);

- if (vkms->output.composer_workq)
- destroy_workqueue(vkms->output.composer_workq);
+ for (int i = 0; i < vkms->output.num_crtcs; i++)
+ destroy_workqueue(vkms->output.crtcs[i].composer_workq);
}

static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
@@ -90,37 +102,12 @@ static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
drm_atomic_helper_cleanup_planes(dev, old_state);
}

-static int vkms_config_show(struct seq_file *m, void *data)
-{
- struct drm_info_node *node = (struct drm_info_node *)m->private;
- struct drm_device *dev = node->minor->dev;
- struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
-
- seq_printf(m, "writeback=%d\n", vkmsdev->config->writeback);
- seq_printf(m, "cursor=%d\n", vkmsdev->config->cursor);
- seq_printf(m, "overlay=%d\n", vkmsdev->config->overlay);
-
- return 0;
-}
-
-static const struct drm_info_list vkms_config_debugfs_list[] = {
- { "vkms_config", vkms_config_show, 0 },
-};
-
-static void vkms_config_debugfs_init(struct drm_minor *minor)
-{
- drm_debugfs_create_files(vkms_config_debugfs_list, ARRAY_SIZE(vkms_config_debugfs_list),
- minor->debugfs_root, minor);
-}
-
static const struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM,
.release = vkms_release,
.fops = &vkms_driver_fops,
DRM_GEM_SHMEM_DRIVER_OPS,

- .debugfs_init = vkms_config_debugfs_init,
-
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
@@ -141,8 +128,12 @@ static const struct drm_mode_config_helper_funcs vkms_mode_config_helpers = {
static int vkms_modeset_init(struct vkms_device *vkmsdev)
{
struct drm_device *dev = &vkmsdev->drm;
+ int ret;
+
+ ret = drmm_mode_config_init(dev);
+ if (ret)
+ return ret;

- drm_mode_config_init(dev);
dev->mode_config.funcs = &vkms_mode_funcs;
dev->mode_config.min_width = XRES_MIN;
dev->mode_config.min_height = YRES_MIN;
@@ -156,114 +147,176 @@ static int vkms_modeset_init(struct vkms_device *vkmsdev)
dev->mode_config.preferred_depth = 0;
dev->mode_config.helper_private = &vkms_mode_config_helpers;

- return vkms_output_init(vkmsdev, 0);
+ return vkmsdev->configfs ? vkms_output_init(vkmsdev) :
+ vkms_output_init_default(vkmsdev);
}

-static int vkms_create(struct vkms_config *config)
+static int vkms_platform_probe(struct platform_device *pdev)
{
int ret;
- struct platform_device *pdev;
+ struct vkms_device_setup *vkms_device_setup = pdev->dev.platform_data;
struct vkms_device *vkms_device;
+ void *grp;

- pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
- if (IS_ERR(pdev))
- return PTR_ERR(pdev);
-
- if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) {
- ret = -ENOMEM;
- goto out_unregister;
+ grp = devres_open_group(&pdev->dev, NULL, GFP_KERNEL);
+ if (!grp) {
+ DRM_ERROR("Could not open devres group\n");
+ return -ENOMEM;
}

vkms_device = devm_drm_dev_alloc(&pdev->dev, &vkms_driver,
struct vkms_device, drm);
if (IS_ERR(vkms_device)) {
ret = PTR_ERR(vkms_device);
- goto out_devres;
+ goto out_release_group;
}
+
vkms_device->platform = pdev;
- vkms_device->config = config;
- config->dev = vkms_device;
+ vkms_device->config.cursor = enable_cursor;
+ vkms_device->config.writeback = enable_writeback;
+ vkms_device->config.overlay = enable_overlay;
+ vkms_device->configfs = vkms_device_setup->configfs;

ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev,
DMA_BIT_MASK(64));
-
if (ret) {
DRM_ERROR("Could not initialize DMA support\n");
- goto out_devres;
+ goto out_release_group;
}

- ret = drm_vblank_init(&vkms_device->drm, 1);
+ ret = vkms_modeset_init(vkms_device);
if (ret) {
- DRM_ERROR("Failed to vblank\n");
- goto out_devres;
+ DRM_ERROR("Unable to initialize modesetting\n");
+ goto out_release_group;
}

- ret = vkms_modeset_init(vkms_device);
- if (ret)
- goto out_devres;
+ ret = drm_vblank_init(&vkms_device->drm, vkms_device->output.num_crtcs);
+ if (ret) {
+ DRM_ERROR("Failed to vblank\n");
+ goto out_release_group;
+ }

ret = drm_dev_register(&vkms_device->drm, 0);
- if (ret)
- goto out_devres;
+ if (ret) {
+ DRM_ERROR("Unable to register device with id %d\n", pdev->id);
+ goto out_release_group;
+ }

drm_fbdev_generic_setup(&vkms_device->drm, 0);
+ platform_set_drvdata(pdev, vkms_device);
+ devres_close_group(&pdev->dev, grp);

return 0;

-out_devres:
- devres_release_group(&pdev->dev, NULL);
-out_unregister:
- platform_device_unregister(pdev);
+out_release_group:
+ devres_release_group(&pdev->dev, grp);
return ret;
}

-static int __init vkms_init(void)
+static int vkms_platform_remove(struct platform_device *pdev)
{
- int ret;
- struct vkms_config *config;
+ struct vkms_device *vkms_device;

- config = kmalloc(sizeof(*config), GFP_KERNEL);
- if (!config)
- return -ENOMEM;
+ vkms_device = platform_get_drvdata(pdev);
+ if (!vkms_device)
+ return 0;

- default_config = config;
+ drm_dev_unregister(&vkms_device->drm);
+ drm_atomic_helper_shutdown(&vkms_device->drm);
+ return 0;
+}

- config->cursor = enable_cursor;
- config->writeback = enable_writeback;
- config->overlay = enable_overlay;
+static struct platform_driver vkms_platform_driver = {
+ .probe = vkms_platform_probe,
+ .remove = vkms_platform_remove,
+ .driver.name = DRIVER_NAME,
+};

- ret = vkms_create(config);
- if (ret)
- kfree(config);
+struct vkms_device *vkms_add_device(struct vkms_configfs *configfs)
+{
+ struct device *dev = NULL;
+ struct platform_device *pdev;
+ int max_id = 1;
+ struct vkms_device_setup vkms_device_setup = {
+ .configfs = configfs,
+ };
+
+ while ((dev = platform_find_device_by_driver(
+ dev, &vkms_platform_driver.driver))) {
+ pdev = to_platform_device(dev);
+ max_id = max(max_id, pdev->id);
+ put_device(dev);
+ }

- return ret;
+ pdev = platform_device_register_data(NULL, DRIVER_NAME, max_id + 1,
+ &vkms_device_setup,
+ sizeof(vkms_device_setup));
+ if (IS_ERR(pdev)) {
+ DRM_ERROR("Unable to register vkms device'\n");
+ return ERR_PTR(PTR_ERR(pdev));
+ }
+
+ return platform_get_drvdata(pdev);
}

-static void vkms_destroy(struct vkms_config *config)
+void vkms_remove_device(struct vkms_device *vkms_device)
{
- struct platform_device *pdev;
+ platform_device_unregister(vkms_device->platform);
+}
+
+static int __init vkms_init(void)
+{
+ int ret;
+ struct platform_device *default_pdev = NULL;

- if (!config->dev) {
- DRM_INFO("vkms_device is NULL.\n");
- return;
+ ret = platform_driver_register(&vkms_platform_driver);
+ if (ret) {
+ DRM_ERROR("Unable to register platform driver\n");
+ return ret;
}

- pdev = config->dev->platform;
+ if (enable_default_device) {
+ struct vkms_device_setup vkms_device_setup = {
+ .configfs = NULL,
+ };
+
+ default_pdev = platform_device_register_data(
+ NULL, DRIVER_NAME, 0, &vkms_device_setup,
+ sizeof(vkms_device_setup));
+ if (IS_ERR(default_pdev)) {
+ DRM_ERROR("Unable to register default vkms device\n");
+ platform_driver_unregister(&vkms_platform_driver);
+ return PTR_ERR(default_pdev);
+ }
+ }

- drm_dev_unregister(&config->dev->drm);
- drm_atomic_helper_shutdown(&config->dev->drm);
- devres_release_group(&pdev->dev, NULL);
- platform_device_unregister(pdev);
+ ret = vkms_init_configfs();
+ if (ret) {
+ DRM_ERROR("Unable to initialize configfs\n");
+ if (default_pdev)
+ platform_device_unregister(default_pdev);
+
+ platform_driver_unregister(&vkms_platform_driver);
+ }

- config->dev = NULL;
+ return 0;
}

static void __exit vkms_exit(void)
{
- if (default_config->dev)
- vkms_destroy(default_config);
+ struct device *dev;
+
+ vkms_unregister_configfs();
+
+ while ((dev = platform_find_device_by_driver(
+ NULL, &vkms_platform_driver.driver))) {
+ // platform_find_device_by_driver increments the refcount. Drop
+ // it so we don't leak memory.
+ put_device(dev);
+ platform_device_unregister(to_platform_device(dev));
+ }

- kfree(default_config);
+ platform_driver_unregister(&vkms_platform_driver);
}

module_init(vkms_init);
diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
index 0a67b8073f7e..7b9b189511c2 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.h
+++ b/drivers/gpu/drm/vkms/vkms_drv.h
@@ -3,6 +3,8 @@
#ifndef _VKMS_DRV_H_
#define _VKMS_DRV_H_

+#include "drm/drm_connector.h"
+#include <linux/configfs.h>
#include <linux/hrtimer.h>

#include <drm/drm.h>
@@ -10,10 +12,11 @@
#include <drm/drm_gem.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_encoder.h>
+#include <drm/drm_plane.h>
#include <drm/drm_writeback.h>

-#define XRES_MIN 20
-#define YRES_MIN 20
+#define XRES_MIN 10
+#define YRES_MIN 10

#define XRES_DEF 1024
#define YRES_DEF 768
@@ -23,10 +26,15 @@

#define NUM_OVERLAY_PLANES 8

+#define VKMS_MAX_OUTPUT_OBJECTS 16
+#define VKMS_MAX_PLANES (3 * VKMS_MAX_OUTPUT_OBJECTS)
+
struct vkms_frame_info {
struct drm_framebuffer *fb;
struct drm_rect src, dst;
+ struct drm_rect rotated;
struct iosys_map map[DRM_FORMAT_MAX_PLANES];
+ unsigned int rotation;
unsigned int offset;
unsigned int pitch;
unsigned int cpp;
@@ -44,8 +52,7 @@ struct line_buffer {
struct vkms_writeback_job {
struct iosys_map data[DRM_FORMAT_MAX_PLANES];
struct vkms_frame_info wb_frame_info;
- void (*wb_write)(struct vkms_frame_info *frame_info,
- const struct line_buffer *buffer, int y);
+ void (*pixel_write)(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel);
};

/**
@@ -56,14 +63,32 @@ struct vkms_writeback_job {
struct vkms_plane_state {
struct drm_shadow_plane_state base;
struct vkms_frame_info *frame_info;
- void (*plane_read)(struct line_buffer *buffer,
- const struct vkms_frame_info *frame_info, int y);
+ void (*pixel_read)(u8 *src_buffer, struct pixel_argb_u16 *out_pixel);
};

struct vkms_plane {
struct drm_plane base;
};

+struct vkms_crtc {
+ struct drm_crtc base;
+
+ struct drm_writeback_connector wb_connector;
+ struct hrtimer vblank_hrtimer;
+ ktime_t period_ns;
+ struct drm_pending_vblank_event *event;
+ /* ordered wq for composer_work */
+ struct workqueue_struct *composer_workq;
+ /* protects concurrent access to composer */
+ spinlock_t lock;
+
+ /* protected by @lock */
+ bool composer_enabled;
+ struct vkms_crtc_state *composer_state;
+
+ spinlock_t composer_lock;
+};
+
/**
* vkms_crtc_state - Driver specific CRTC state
* @base: base CRTC state
@@ -88,62 +113,145 @@ struct vkms_crtc_state {
};

struct vkms_output {
- struct drm_crtc crtc;
- struct drm_encoder encoder;
- struct drm_connector connector;
- struct drm_writeback_connector wb_connector;
- struct hrtimer vblank_hrtimer;
- ktime_t period_ns;
- struct drm_pending_vblank_event *event;
- /* ordered wq for composer_work */
- struct workqueue_struct *composer_workq;
- /* protects concurrent access to composer */
- spinlock_t lock;
-
- /* protected by @lock */
- bool composer_enabled;
- struct vkms_crtc_state *composer_state;
-
- spinlock_t composer_lock;
+ int num_crtcs;
+ struct vkms_crtc crtcs[VKMS_MAX_OUTPUT_OBJECTS];
+ int num_encoders;
+ struct drm_encoder encoders[VKMS_MAX_OUTPUT_OBJECTS];
+ int num_connectors;
+ struct drm_connector connectors[VKMS_MAX_OUTPUT_OBJECTS];
+ int num_planes;
+ struct vkms_plane planes[VKMS_MAX_PLANES];
};

-struct vkms_device;
-
struct vkms_config {
bool writeback;
bool cursor;
bool overlay;
- /* only set when instantiated */
- struct vkms_device *dev;
+};
+
+struct vkms_config_links {
+ struct config_group group;
+ unsigned long linked_object_bitmap;
+};
+
+struct vkms_config_connector {
+ struct config_group config_group;
+ struct drm_connector *connector;
+ struct vkms_config_links possible_encoders;
+ bool connected;
+};
+
+struct vkms_config_crtc {
+ struct config_group config_group;
+ unsigned long crtc_config_idx;
+};
+
+struct vkms_config_encoder {
+ struct config_group config_group;
+ struct vkms_config_links possible_crtcs;
+ unsigned long encoder_config_idx;
+};
+
+struct vkms_config_plane {
+ struct vkms_configfs *configfs;
+ struct config_group config_group;
+ struct vkms_config_links possible_crtcs;
+ enum drm_plane_type type;
+};
+
+struct vkms_configfs {
+ /* Directory group containing connector configs, e.g. /config/vkms/device/ */
+ struct config_group device_group;
+ /* Directory group containing connector configs, e.g. /config/vkms/device/connectors/ */
+ struct config_group connectors_group;
+ /* Directory group containing CRTC configs, e.g. /config/vkms/device/crtcs/ */
+ struct config_group crtcs_group;
+ /* Directory group containing encoder configs, e.g. /config/vkms/device/encoders/ */
+ struct config_group encoders_group;
+ /* Directory group containing plane configs, e.g. /config/vkms/device/planes/ */
+ struct config_group planes_group;
+
+ unsigned long allocated_crtcs;
+ unsigned long allocated_encoders;
+
+ struct mutex lock;
+
+ /* The platform device if this is registered, otherwise NULL */
+ struct vkms_device *vkms_device;
+};
+
+struct vkms_device_setup {
+ // Is NULL in the case of the default card.
+ struct vkms_configfs *configfs;
};

struct vkms_device {
struct drm_device drm;
struct platform_device *platform;
+ // Is NULL in the case of the default card.
+ struct vkms_configfs *configfs;
struct vkms_output output;
- const struct vkms_config *config;
+ struct vkms_config config;
};

-#define drm_crtc_to_vkms_output(target) \
- container_of(target, struct vkms_output, crtc)
+#define drm_crtc_to_vkms_crtc(crtc) container_of(crtc, struct vkms_crtc, base)

#define drm_device_to_vkms_device(target) \
container_of(target, struct vkms_device, drm)

+#define timer_to_vkms_crtc(timer) \
+ container_of(timer, struct vkms_crtc, vblank_hrtimer)
+
#define to_vkms_crtc_state(target)\
container_of(target, struct vkms_crtc_state, base)

#define to_vkms_plane_state(target)\
container_of(target, struct vkms_plane_state, base.base)

+#define item_to_configfs(item) \
+ container_of(to_config_group(item), struct vkms_configfs, device_group)
+
+#define connector_item_to_configfs(item) \
+ container_of(to_config_group(item->ci_parent), struct vkms_configfs, \
+ connectors_group)
+
+#define item_to_config_connector(item) \
+ container_of(to_config_group(item), struct vkms_config_connector, \
+ config_group)
+
+#define item_to_config_crtc(item) \
+ container_of(to_config_group(item), struct vkms_config_crtc, \
+ config_group)
+
+#define item_to_config_encoder(item) \
+ container_of(to_config_group(item), struct vkms_config_encoder, \
+ config_group)
+
+#define item_to_config_plane(item) \
+ container_of(to_config_group(item), struct vkms_config_plane, \
+ config_group)
+
+#define item_to_config_links(item) \
+ container_of(to_config_group(item), struct vkms_config_links, group)
+
+#define plane_item_to_configfs(item) \
+ container_of(to_config_group(item->ci_parent), struct vkms_configfs, \
+ planes_group)
+
+/* Devices */
+struct vkms_device *vkms_add_device(struct vkms_configfs *configfs);
+void vkms_remove_device(struct vkms_device *vkms_device);
+
/* CRTC */
-int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
- struct drm_plane *primary, struct drm_plane *cursor);
+struct vkms_crtc *vkms_crtc_init(struct vkms_device *vkmsdev,
+ struct drm_plane *primary,
+ struct drm_plane *cursor, const char *name);

-int vkms_output_init(struct vkms_device *vkmsdev, int index);
+int vkms_output_init(struct vkms_device *vkmsdev);
+int vkms_output_init_default(struct vkms_device *vkmsdev);

struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
- enum drm_plane_type type, int index);
+ enum drm_plane_type type);

/* CRC Support */
const char *const *vkms_get_crc_sources(struct drm_crtc *crtc,
@@ -154,9 +262,20 @@ int vkms_verify_crc_source(struct drm_crtc *crtc, const char *source_name,

/* Composer Support */
void vkms_composer_worker(struct work_struct *work);
-void vkms_set_composer(struct vkms_output *out, bool enabled);
+void vkms_set_composer(struct vkms_crtc *vkms_crtc, bool enabled);
+void vkms_compose_row(struct line_buffer *stage_buffer, struct vkms_plane_state *plane, int y);
+void vkms_writeback_row(struct vkms_writeback_job *wb, const struct line_buffer *src_buffer, int y);

/* Writeback */
-int vkms_enable_writeback_connector(struct vkms_device *vkmsdev);
+int vkms_enable_writeback_connector(struct vkms_device *vkmsdev,
+ struct vkms_crtc *vkms_crtc);
+
+/* ConfigFS Support */
+int vkms_init_configfs(void);
+void vkms_unregister_configfs(void);
+
+/* Connector hotplugging */
+enum drm_connector_status vkms_connector_detect(struct drm_connector *connector,
+ bool force);

#endif /* _VKMS_DRV_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_formats.c b/drivers/gpu/drm/vkms/vkms_formats.c
index d4950688b3f1..36046b12f296 100644
--- a/drivers/gpu/drm/vkms/vkms_formats.c
+++ b/drivers/gpu/drm/vkms/vkms_formats.c
@@ -2,6 +2,8 @@

#include <linux/kernel.h>
#include <linux/minmax.h>
+
+#include <drm/drm_blend.h>
#include <drm/drm_rect.h>
#include <drm/drm_fixed.h>

@@ -37,104 +39,106 @@ static void *packed_pixels_addr(const struct vkms_frame_info *frame_info,
static void *get_packed_src_addr(const struct vkms_frame_info *frame_info, int y)
{
int x_src = frame_info->src.x1 >> 16;
- int y_src = y - frame_info->dst.y1 + (frame_info->src.y1 >> 16);
+ int y_src = y - frame_info->rotated.y1 + (frame_info->src.y1 >> 16);

return packed_pixels_addr(frame_info, x_src, y_src);
}

-static void ARGB8888_to_argb_u16(struct line_buffer *stage_buffer,
- const struct vkms_frame_info *frame_info, int y)
+static int get_x_position(const struct vkms_frame_info *frame_info, int limit, int x)
{
- struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
- u8 *src_pixels = get_packed_src_addr(frame_info, y);
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- stage_buffer->n_pixels);
-
- for (size_t x = 0; x < x_limit; x++, src_pixels += 4) {
- /*
- * The 257 is the "conversion ratio". This number is obtained by the
- * (2^16 - 1) / (2^8 - 1) division. Which, in this case, tries to get
- * the best color value in a pixel format with more possibilities.
- * A similar idea applies to others RGB color conversions.
- */
- out_pixels[x].a = (u16)src_pixels[3] * 257;
- out_pixels[x].r = (u16)src_pixels[2] * 257;
- out_pixels[x].g = (u16)src_pixels[1] * 257;
- out_pixels[x].b = (u16)src_pixels[0] * 257;
- }
+ if (frame_info->rotation & (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_270))
+ return limit - x - 1;
+ return x;
}

-static void XRGB8888_to_argb_u16(struct line_buffer *stage_buffer,
- const struct vkms_frame_info *frame_info, int y)
+static void ARGB8888_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
{
- struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
- u8 *src_pixels = get_packed_src_addr(frame_info, y);
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- stage_buffer->n_pixels);
-
- for (size_t x = 0; x < x_limit; x++, src_pixels += 4) {
- out_pixels[x].a = (u16)0xffff;
- out_pixels[x].r = (u16)src_pixels[2] * 257;
- out_pixels[x].g = (u16)src_pixels[1] * 257;
- out_pixels[x].b = (u16)src_pixels[0] * 257;
- }
+ /*
+ * The 257 is the "conversion ratio". This number is obtained by the
+ * (2^16 - 1) / (2^8 - 1) division. Which, in this case, tries to get
+ * the best color value in a pixel format with more possibilities.
+ * A similar idea applies to others RGB color conversions.
+ */
+ out_pixel->a = (u16)src_pixels[3] * 257;
+ out_pixel->r = (u16)src_pixels[2] * 257;
+ out_pixel->g = (u16)src_pixels[1] * 257;
+ out_pixel->b = (u16)src_pixels[0] * 257;
}

-static void ARGB16161616_to_argb_u16(struct line_buffer *stage_buffer,
- const struct vkms_frame_info *frame_info,
- int y)
+static void XRGB8888_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
{
- struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
- u16 *src_pixels = get_packed_src_addr(frame_info, y);
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- stage_buffer->n_pixels);
-
- for (size_t x = 0; x < x_limit; x++, src_pixels += 4) {
- out_pixels[x].a = le16_to_cpu(src_pixels[3]);
- out_pixels[x].r = le16_to_cpu(src_pixels[2]);
- out_pixels[x].g = le16_to_cpu(src_pixels[1]);
- out_pixels[x].b = le16_to_cpu(src_pixels[0]);
- }
+ out_pixel->a = (u16)0xffff;
+ out_pixel->r = (u16)src_pixels[2] * 257;
+ out_pixel->g = (u16)src_pixels[1] * 257;
+ out_pixel->b = (u16)src_pixels[0] * 257;
}

-static void XRGB16161616_to_argb_u16(struct line_buffer *stage_buffer,
- const struct vkms_frame_info *frame_info,
- int y)
+static void ARGB16161616_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
{
- struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
- u16 *src_pixels = get_packed_src_addr(frame_info, y);
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- stage_buffer->n_pixels);
-
- for (size_t x = 0; x < x_limit; x++, src_pixels += 4) {
- out_pixels[x].a = (u16)0xffff;
- out_pixels[x].r = le16_to_cpu(src_pixels[2]);
- out_pixels[x].g = le16_to_cpu(src_pixels[1]);
- out_pixels[x].b = le16_to_cpu(src_pixels[0]);
- }
+ u16 *pixels = (u16 *)src_pixels;
+
+ out_pixel->a = le16_to_cpu(pixels[3]);
+ out_pixel->r = le16_to_cpu(pixels[2]);
+ out_pixel->g = le16_to_cpu(pixels[1]);
+ out_pixel->b = le16_to_cpu(pixels[0]);
}

-static void RGB565_to_argb_u16(struct line_buffer *stage_buffer,
- const struct vkms_frame_info *frame_info, int y)
+static void XRGB16161616_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
{
- struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
- u16 *src_pixels = get_packed_src_addr(frame_info, y);
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- stage_buffer->n_pixels);
+ u16 *pixels = (u16 *)src_pixels;
+
+ out_pixel->a = (u16)0xffff;
+ out_pixel->r = le16_to_cpu(pixels[2]);
+ out_pixel->g = le16_to_cpu(pixels[1]);
+ out_pixel->b = le16_to_cpu(pixels[0]);
+}
+
+static void RGB565_to_argb_u16(u8 *src_pixels, struct pixel_argb_u16 *out_pixel)
+{
+ u16 *pixels = (u16 *)src_pixels;

s64 fp_rb_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(31));
s64 fp_g_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(63));

- for (size_t x = 0; x < x_limit; x++, src_pixels++) {
- u16 rgb_565 = le16_to_cpu(*src_pixels);
- s64 fp_r = drm_int2fixp((rgb_565 >> 11) & 0x1f);
- s64 fp_g = drm_int2fixp((rgb_565 >> 5) & 0x3f);
- s64 fp_b = drm_int2fixp(rgb_565 & 0x1f);
+ u16 rgb_565 = le16_to_cpu(*pixels);
+ s64 fp_r = drm_int2fixp((rgb_565 >> 11) & 0x1f);
+ s64 fp_g = drm_int2fixp((rgb_565 >> 5) & 0x3f);
+ s64 fp_b = drm_int2fixp(rgb_565 & 0x1f);
+
+ out_pixel->a = (u16)0xffff;
+ out_pixel->r = drm_fixp2int_round(drm_fixp_mul(fp_r, fp_rb_ratio));
+ out_pixel->g = drm_fixp2int_round(drm_fixp_mul(fp_g, fp_g_ratio));
+ out_pixel->b = drm_fixp2int_round(drm_fixp_mul(fp_b, fp_rb_ratio));
+}
+
+/**
+ * vkms_compose_row - compose a single row of a plane
+ * @stage_buffer: output line with the composed pixels
+ * @plane: state of the plane that is being composed
+ * @y: y coordinate of the row
+ *
+ * This function composes a single row of a plane. It gets the source pixels
+ * through the y coordinate (see get_packed_src_addr()) and goes linearly
+ * through the source pixel, reading the pixels and converting it to
+ * ARGB16161616 (see the pixel_read() callback). For rotate-90 and rotate-270,
+ * the source pixels are not traversed linearly. The source pixels are queried
+ * on each iteration in order to traverse the pixels vertically.
+ */
+void vkms_compose_row(struct line_buffer *stage_buffer, struct vkms_plane_state *plane, int y)
+{
+ struct pixel_argb_u16 *out_pixels = stage_buffer->pixels;
+ struct vkms_frame_info *frame_info = plane->frame_info;
+ u8 *src_pixels = get_packed_src_addr(frame_info, y);
+ int limit = min_t(size_t, drm_rect_width(&frame_info->dst), stage_buffer->n_pixels);
+
+ for (size_t x = 0; x < limit; x++, src_pixels += frame_info->cpp) {
+ int x_pos = get_x_position(frame_info, limit, x);
+
+ if (drm_rotation_90_or_270(frame_info->rotation))
+ src_pixels = get_packed_src_addr(frame_info, x + frame_info->rotated.y1)
+ + frame_info->cpp * y;

- out_pixels[x].a = (u16)0xffff;
- out_pixels[x].r = drm_fixp2int(drm_fixp_mul(fp_r, fp_rb_ratio));
- out_pixels[x].g = drm_fixp2int(drm_fixp_mul(fp_g, fp_g_ratio));
- out_pixels[x].b = drm_fixp2int(drm_fixp_mul(fp_b, fp_rb_ratio));
+ plane->pixel_read(src_pixels, &out_pixels[x_pos]);
}
}

@@ -146,110 +150,84 @@ static void RGB565_to_argb_u16(struct line_buffer *stage_buffer,
* They are used in the `compose_active_planes` to convert and store a line
* from the src_buffer to the writeback buffer.
*/
-static void argb_u16_to_ARGB8888(struct vkms_frame_info *frame_info,
- const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_ARGB8888(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
{
- int x_dst = frame_info->dst.x1;
- u8 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
- struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- src_buffer->n_pixels);
-
- for (size_t x = 0; x < x_limit; x++, dst_pixels += 4) {
- /*
- * This sequence below is important because the format's byte order is
- * in little-endian. In the case of the ARGB8888 the memory is
- * organized this way:
- *
- * | Addr | = blue channel
- * | Addr + 1 | = green channel
- * | Addr + 2 | = Red channel
- * | Addr + 3 | = Alpha channel
- */
- dst_pixels[3] = DIV_ROUND_CLOSEST(in_pixels[x].a, 257);
- dst_pixels[2] = DIV_ROUND_CLOSEST(in_pixels[x].r, 257);
- dst_pixels[1] = DIV_ROUND_CLOSEST(in_pixels[x].g, 257);
- dst_pixels[0] = DIV_ROUND_CLOSEST(in_pixels[x].b, 257);
- }
+ /*
+ * This sequence below is important because the format's byte order is
+ * in little-endian. In the case of the ARGB8888 the memory is
+ * organized this way:
+ *
+ * | Addr | = blue channel
+ * | Addr + 1 | = green channel
+ * | Addr + 2 | = Red channel
+ * | Addr + 3 | = Alpha channel
+ */
+ dst_pixels[3] = DIV_ROUND_CLOSEST(in_pixel->a, 257);
+ dst_pixels[2] = DIV_ROUND_CLOSEST(in_pixel->r, 257);
+ dst_pixels[1] = DIV_ROUND_CLOSEST(in_pixel->g, 257);
+ dst_pixels[0] = DIV_ROUND_CLOSEST(in_pixel->b, 257);
}

-static void argb_u16_to_XRGB8888(struct vkms_frame_info *frame_info,
- const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_XRGB8888(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
{
- int x_dst = frame_info->dst.x1;
- u8 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
- struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- src_buffer->n_pixels);
-
- for (size_t x = 0; x < x_limit; x++, dst_pixels += 4) {
- dst_pixels[3] = 0xff;
- dst_pixels[2] = DIV_ROUND_CLOSEST(in_pixels[x].r, 257);
- dst_pixels[1] = DIV_ROUND_CLOSEST(in_pixels[x].g, 257);
- dst_pixels[0] = DIV_ROUND_CLOSEST(in_pixels[x].b, 257);
- }
+ dst_pixels[3] = 0xff;
+ dst_pixels[2] = DIV_ROUND_CLOSEST(in_pixel->r, 257);
+ dst_pixels[1] = DIV_ROUND_CLOSEST(in_pixel->g, 257);
+ dst_pixels[0] = DIV_ROUND_CLOSEST(in_pixel->b, 257);
}

-static void argb_u16_to_ARGB16161616(struct vkms_frame_info *frame_info,
- const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_ARGB16161616(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
{
- int x_dst = frame_info->dst.x1;
- u16 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
- struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- src_buffer->n_pixels);
-
- for (size_t x = 0; x < x_limit; x++, dst_pixels += 4) {
- dst_pixels[3] = cpu_to_le16(in_pixels[x].a);
- dst_pixels[2] = cpu_to_le16(in_pixels[x].r);
- dst_pixels[1] = cpu_to_le16(in_pixels[x].g);
- dst_pixels[0] = cpu_to_le16(in_pixels[x].b);
- }
+ u16 *pixels = (u16 *)dst_pixels;
+
+ pixels[3] = cpu_to_le16(in_pixel->a);
+ pixels[2] = cpu_to_le16(in_pixel->r);
+ pixels[1] = cpu_to_le16(in_pixel->g);
+ pixels[0] = cpu_to_le16(in_pixel->b);
}

-static void argb_u16_to_XRGB16161616(struct vkms_frame_info *frame_info,
- const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_XRGB16161616(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
{
- int x_dst = frame_info->dst.x1;
- u16 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
- struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- src_buffer->n_pixels);
-
- for (size_t x = 0; x < x_limit; x++, dst_pixels += 4) {
- dst_pixels[3] = 0xffff;
- dst_pixels[2] = cpu_to_le16(in_pixels[x].r);
- dst_pixels[1] = cpu_to_le16(in_pixels[x].g);
- dst_pixels[0] = cpu_to_le16(in_pixels[x].b);
- }
+ u16 *pixels = (u16 *)dst_pixels;
+
+ pixels[3] = 0xffff;
+ pixels[2] = cpu_to_le16(in_pixel->r);
+ pixels[1] = cpu_to_le16(in_pixel->g);
+ pixels[0] = cpu_to_le16(in_pixel->b);
}

-static void argb_u16_to_RGB565(struct vkms_frame_info *frame_info,
- const struct line_buffer *src_buffer, int y)
+static void argb_u16_to_RGB565(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
{
- int x_dst = frame_info->dst.x1;
- u16 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
- struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
- int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst),
- src_buffer->n_pixels);
+ u16 *pixels = (u16 *)dst_pixels;

s64 fp_rb_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(31));
s64 fp_g_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(63));

- for (size_t x = 0; x < x_limit; x++, dst_pixels++) {
- s64 fp_r = drm_int2fixp(in_pixels[x].r);
- s64 fp_g = drm_int2fixp(in_pixels[x].g);
- s64 fp_b = drm_int2fixp(in_pixels[x].b);
+ s64 fp_r = drm_int2fixp(in_pixel->r);
+ s64 fp_g = drm_int2fixp(in_pixel->g);
+ s64 fp_b = drm_int2fixp(in_pixel->b);

- u16 r = drm_fixp2int(drm_fixp_div(fp_r, fp_rb_ratio));
- u16 g = drm_fixp2int(drm_fixp_div(fp_g, fp_g_ratio));
- u16 b = drm_fixp2int(drm_fixp_div(fp_b, fp_rb_ratio));
+ u16 r = drm_fixp2int(drm_fixp_div(fp_r, fp_rb_ratio));
+ u16 g = drm_fixp2int(drm_fixp_div(fp_g, fp_g_ratio));
+ u16 b = drm_fixp2int(drm_fixp_div(fp_b, fp_rb_ratio));

- *dst_pixels = cpu_to_le16(r << 11 | g << 5 | b);
- }
+ *pixels = cpu_to_le16(r << 11 | g << 5 | b);
+}
+
+void vkms_writeback_row(struct vkms_writeback_job *wb,
+ const struct line_buffer *src_buffer, int y)
+{
+ struct vkms_frame_info *frame_info = &wb->wb_frame_info;
+ int x_dst = frame_info->dst.x1;
+ u8 *dst_pixels = packed_pixels_addr(frame_info, x_dst, y);
+ struct pixel_argb_u16 *in_pixels = src_buffer->pixels;
+ int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst), src_buffer->n_pixels);
+
+ for (size_t x = 0; x < x_limit; x++, dst_pixels += frame_info->cpp)
+ wb->pixel_write(dst_pixels, &in_pixels[x]);
}

-void *get_frame_to_line_function(u32 format)
+void *get_pixel_conversion_function(u32 format)
{
switch (format) {
case DRM_FORMAT_ARGB8888:
@@ -267,7 +245,7 @@ void *get_frame_to_line_function(u32 format)
}
}

-void *get_line_to_frame_function(u32 format)
+void *get_pixel_write_function(u32 format)
{
switch (format) {
case DRM_FORMAT_ARGB8888:
diff --git a/drivers/gpu/drm/vkms/vkms_formats.h b/drivers/gpu/drm/vkms/vkms_formats.h
index 43b7c1979018..cf59c2ed8e9a 100644
--- a/drivers/gpu/drm/vkms/vkms_formats.h
+++ b/drivers/gpu/drm/vkms/vkms_formats.h
@@ -5,8 +5,8 @@

#include "vkms_drv.h"

-void *get_frame_to_line_function(u32 format);
+void *get_pixel_conversion_function(u32 format);

-void *get_line_to_frame_function(u32 format);
+void *get_pixel_write_function(u32 format);

#endif /* _VKMS_FORMATS_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 991857125bb4..28a50d2149f5 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -1,24 +1,73 @@
// SPDX-License-Identifier: GPL-2.0+

-#include "vkms_drv.h"
+#include <drm/drm_print.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_plane.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
+#include <linux/printk.h>

-static void vkms_connector_destroy(struct drm_connector *connector)
-{
- drm_connector_cleanup(connector);
-}
+#include "vkms_drv.h"

static const struct drm_connector_funcs vkms_connector_funcs = {
+ .detect = vkms_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = vkms_connector_destroy,
+ .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,
};

+static const struct vkms_config_connector *
+find_config_for_connector(struct drm_connector *connector)
+{
+ struct vkms_device *vkms = drm_device_to_vkms_device(connector->dev);
+ struct vkms_configfs *configfs = vkms->configfs;
+ struct config_item *item;
+
+ if (!configfs) {
+ pr_info("Default connector has no configfs entry");
+ return NULL;
+ }
+
+ list_for_each_entry(item, &configfs->connectors_group.cg_children,
+ ci_entry) {
+ struct vkms_config_connector *config_connector =
+ item_to_config_connector(item);
+ if (config_connector->connector == connector)
+ return config_connector;
+ }
+
+ pr_warn("Could not find config to match connector %s, but configfs was initialized",
+ connector->name);
+
+ return NULL;
+}
+
+enum drm_connector_status vkms_connector_detect(struct drm_connector *connector,
+ bool force)
+{
+ enum drm_connector_status status = connector_status_connected;
+ const struct vkms_config_connector *config_connector =
+ find_config_for_connector(connector);
+
+ if (!config_connector)
+ return connector_status_connected;
+
+ if (!config_connector->connected)
+ status = connector_status_disconnected;
+
+ return status;
+}
+
+static const struct drm_encoder_funcs vkms_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
static int vkms_conn_get_modes(struct drm_connector *connector)
{
int count;
@@ -30,82 +79,120 @@ static int vkms_conn_get_modes(struct drm_connector *connector)
}

static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
- .get_modes = vkms_conn_get_modes,
+ .get_modes = vkms_conn_get_modes,
};

-static int vkms_add_overlay_plane(struct vkms_device *vkmsdev, int index,
- struct drm_crtc *crtc)
+static struct drm_connector *
+vkms_connector_init(struct vkms_device *vkms_device)
{
- struct vkms_plane *overlay;
+ struct drm_connector *connector;
+ int ret;
+
+ if (vkms_device->output.num_connectors >= VKMS_MAX_OUTPUT_OBJECTS)
+ return ERR_PTR(-ENOMEM);

- overlay = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_OVERLAY, index);
- if (IS_ERR(overlay))
- return PTR_ERR(overlay);
+ connector = &vkms_device->output
+ .connectors[vkms_device->output.num_connectors++];
+ ret = drm_connector_init(&vkms_device->drm, connector,
+ &vkms_connector_funcs,
+ DRM_MODE_CONNECTOR_VIRTUAL);
+ if (ret) {
+ memset(connector, 0, sizeof(*connector));
+ vkms_device->output.num_connectors -= 1;
+ return ERR_PTR(ret);
+ }

- if (!overlay->base.possible_crtcs)
- overlay->base.possible_crtcs = drm_crtc_mask(crtc);
+ drm_connector_helper_add(connector, &vkms_conn_helper_funcs);

- return 0;
+ return connector;
}

-int vkms_output_init(struct vkms_device *vkmsdev, int index)
+static struct drm_encoder *vkms_encoder_init(struct vkms_device *vkms_device)
+{
+ struct drm_encoder *encoder;
+ int ret;
+
+ if (vkms_device->output.num_encoders >= VKMS_MAX_OUTPUT_OBJECTS)
+ return ERR_PTR(-ENOMEM);
+
+ encoder = &vkms_device->output
+ .encoders[vkms_device->output.num_encoders++];
+ ret = drm_encoder_init(&vkms_device->drm, encoder, &vkms_encoder_funcs,
+ DRM_MODE_ENCODER_VIRTUAL, NULL);
+ if (ret) {
+ memset(encoder, 0, sizeof(*encoder));
+ vkms_device->output.num_encoders -= 1;
+ return ERR_PTR(ret);
+ }
+ return encoder;
+}
+
+int vkms_output_init_default(struct vkms_device *vkmsdev)
{
- struct vkms_output *output = &vkmsdev->output;
struct drm_device *dev = &vkmsdev->drm;
- struct drm_connector *connector = &output->connector;
- struct drm_encoder *encoder = &output->encoder;
- struct drm_crtc *crtc = &output->crtc;
+ struct drm_connector *connector;
+ struct drm_encoder *encoder;
+ struct vkms_crtc *vkms_crtc;
struct vkms_plane *primary, *cursor = NULL;
int ret;
int writeback;
unsigned int n;

- primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY, index);
+ primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY);
if (IS_ERR(primary))
return PTR_ERR(primary);

- if (vkmsdev->config->overlay) {
+ if (vkmsdev->config.overlay) {
for (n = 0; n < NUM_OVERLAY_PLANES; n++) {
- ret = vkms_add_overlay_plane(vkmsdev, index, crtc);
- if (ret)
- return ret;
+ struct vkms_plane *overlay = vkms_plane_init(
+ vkmsdev, DRM_PLANE_TYPE_OVERLAY);
+ if (IS_ERR(overlay)) {
+ return PTR_ERR(overlay);
+ }
}
}

- if (vkmsdev->config->cursor) {
- cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR, index);
- if (IS_ERR(cursor))
+ if (vkmsdev->config.cursor) {
+ cursor = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_CURSOR);
+ if (IS_ERR(cursor)) {
return PTR_ERR(cursor);
+ }
}

- ret = vkms_crtc_init(dev, crtc, &primary->base, &cursor->base);
- if (ret)
- return ret;
+ vkms_crtc = vkms_crtc_init(vkmsdev, &primary->base,
+ cursor ? &cursor->base : NULL,
+ "crtc-default");
+ if (IS_ERR(vkms_crtc)) {
+ DRM_ERROR("Failed to init crtc\n");
+ return PTR_ERR(vkms_crtc);
+ }

- ret = drm_connector_init(dev, connector, &vkms_connector_funcs,
- DRM_MODE_CONNECTOR_VIRTUAL);
- if (ret) {
- DRM_ERROR("Failed to init connector\n");
- goto err_connector;
+ for (int i = 0; i < vkmsdev->output.num_planes; i++) {
+ vkmsdev->output.planes[i].base.possible_crtcs |=
+ drm_crtc_mask(&vkms_crtc->base);
}

- drm_connector_helper_add(connector, &vkms_conn_helper_funcs);
+ connector = vkms_connector_init(vkmsdev);
+ if (IS_ERR(connector)) {
+ DRM_ERROR("Failed to init connector\n");
+ return PTR_ERR(connector);
+ }

- ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL);
- if (ret) {
+ encoder = vkms_encoder_init(vkmsdev);
+ if (IS_ERR(encoder)) {
DRM_ERROR("Failed to init encoder\n");
- goto err_encoder;
+ return PTR_ERR(encoder);
}
- encoder->possible_crtcs = 1;
+ encoder->possible_crtcs |= drm_crtc_mask(&vkms_crtc->base);

ret = drm_connector_attach_encoder(connector, encoder);
if (ret) {
DRM_ERROR("Failed to attach connector to encoder\n");
- goto err_attach;
+ return ret;
}

- if (vkmsdev->config->writeback) {
- writeback = vkms_enable_writeback_connector(vkmsdev);
+ if (vkmsdev->config.writeback) {
+ writeback = vkms_enable_writeback_connector(vkmsdev, vkms_crtc);
if (writeback)
DRM_ERROR("Failed to init writeback connector\n");
}
@@ -113,15 +200,175 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index)
drm_mode_config_reset(dev);

return 0;
+}
+
+static bool is_object_linked(struct vkms_config_links *links, unsigned long idx)
+{
+ return links->linked_object_bitmap & (1 << idx);
+}
+
+int vkms_output_init(struct vkms_device *vkmsdev)
+{
+ struct drm_device *dev = &vkmsdev->drm;
+ struct vkms_configfs *configfs = vkmsdev->configfs;
+ struct vkms_output *output = &vkmsdev->output;
+ struct plane_map {
+ struct vkms_config_plane *config_plane;
+ struct vkms_plane *plane;
+ } plane_map[VKMS_MAX_PLANES] = { 0 };
+ struct encoder_map {
+ struct vkms_config_encoder *config_encoder;
+ struct drm_encoder *encoder;
+ } encoder_map[VKMS_MAX_OUTPUT_OBJECTS] = { 0 };
+ struct config_item *item;
+ int map_idx = 0;
+
+ list_for_each_entry(item, &configfs->planes_group.cg_children,
+ ci_entry) {
+ struct vkms_config_plane *config_plane =
+ item_to_config_plane(item);
+ struct vkms_plane *plane =
+ vkms_plane_init(vkmsdev, config_plane->type);
+
+ if (IS_ERR(plane)) {
+ DRM_ERROR("Unable to init plane from config: %s",
+ item->ci_name);
+ return PTR_ERR(plane);
+ }
+
+ plane_map[map_idx].config_plane = config_plane;
+ plane_map[map_idx].plane = plane;
+ map_idx += 1;
+ }
+
+ map_idx = 0;
+ list_for_each_entry(item, &configfs->encoders_group.cg_children,
+ ci_entry) {
+ struct vkms_config_encoder *config_encoder =
+ item_to_config_encoder(item);
+ struct drm_encoder *encoder = vkms_encoder_init(vkmsdev);
+
+ if (IS_ERR(encoder)) {
+ DRM_ERROR("Failed to init config encoder: %s",
+ item->ci_name);
+ return PTR_ERR(encoder);
+ }
+ encoder_map[map_idx].config_encoder = config_encoder;
+ encoder_map[map_idx].encoder = encoder;
+ map_idx += 1;
+ }
+
+ list_for_each_entry(item, &configfs->connectors_group.cg_children,
+ ci_entry) {
+ struct vkms_config_connector *config_connector =
+ item_to_config_connector(item);
+ struct drm_connector *connector = vkms_connector_init(vkmsdev);
+ if (IS_ERR(connector)) {
+ DRM_ERROR("Failed to init connector from config: %s",
+ item->ci_name);
+ return PTR_ERR(connector);
+ }
+ config_connector->connector = connector;
+
+ for (int j = 0; j < output->num_connectors; j++) {
+ struct encoder_map *encoder = &encoder_map[j];

-err_attach:
- drm_encoder_cleanup(encoder);
+ if (is_object_linked(
+ &config_connector->possible_encoders,
+ encoder->config_encoder
+ ->encoder_config_idx)) {
+ drm_connector_attach_encoder(connector,
+ encoder->encoder);
+ }
+ }
+ }
+
+ list_for_each_entry(item, &configfs->crtcs_group.cg_children,
+ ci_entry) {
+ struct vkms_config_crtc *config_crtc =
+ item_to_config_crtc(item);
+ struct vkms_crtc *vkms_crtc;
+ struct drm_plane *primary = NULL, *cursor = NULL;
+
+ for (int j = 0; j < output->num_planes; j++) {
+ struct plane_map *plane_entry = &plane_map[j];
+ struct drm_plane *plane = &plane_entry->plane->base;
+
+ if (!is_object_linked(
+ &plane_entry->config_plane->possible_crtcs,
+ config_crtc->crtc_config_idx)) {
+ continue;
+ }
+
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
+ if (primary) {
+ DRM_WARN(
+ "Too many primary planes found for crtc %s.",
+ item->ci_name);
+ return EINVAL;
+ }
+ primary = plane;
+ } else if (plane->type == DRM_PLANE_TYPE_CURSOR) {
+ if (cursor) {
+ DRM_WARN(
+ "Too many cursor planes found for crtc %s.",
+ item->ci_name);
+ return EINVAL;
+ }
+ cursor = plane;
+ }
+ }
+
+ if (!primary) {
+ DRM_WARN("No primary plane configured for crtc %s",
+ item->ci_name);
+ return EINVAL;
+ }

-err_encoder:
- drm_connector_cleanup(connector);
+ vkms_crtc =
+ vkms_crtc_init(vkmsdev, primary, cursor, item->ci_name);
+ if (IS_ERR(vkms_crtc)) {
+ DRM_WARN("Unable to init crtc from config: %s",
+ item->ci_name);
+ return PTR_ERR(vkms_crtc);
+ }
+
+ for (int j = 0; j < output->num_planes; j++) {
+ struct plane_map *plane_entry = &plane_map[j];

-err_connector:
- drm_crtc_cleanup(crtc);
+ if (!plane_entry->plane)
+ break;

- return ret;
+ if (is_object_linked(
+ &plane_entry->config_plane->possible_crtcs,
+ config_crtc->crtc_config_idx)) {
+ plane_entry->plane->base.possible_crtcs |=
+ drm_crtc_mask(&vkms_crtc->base);
+ }
+ }
+
+ for (int j = 0; j < output->num_encoders; j++) {
+ struct encoder_map *encoder_entry = &encoder_map[j];
+
+ if (is_object_linked(&encoder_entry->config_encoder
+ ->possible_crtcs,
+ config_crtc->crtc_config_idx)) {
+ encoder_entry->encoder->possible_crtcs |=
+ drm_crtc_mask(&vkms_crtc->base);
+ }
+ }
+
+ if (vkmsdev->config.writeback) {
+ int ret = vkms_enable_writeback_connector(vkmsdev,
+ vkms_crtc);
+ if (ret)
+ DRM_WARN(
+ "Failed to init writeback connector for config crtc: %s. Error code %d",
+ item->ci_name, ret);
+ }
+ }
+
+ drm_mode_config_reset(dev);
+
+ return 0;
}
diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c
index b3f8a115cc23..f69621822c1b 100644
--- a/drivers/gpu/drm/vkms/vkms_plane.c
+++ b/drivers/gpu/drm/vkms/vkms_plane.c
@@ -4,20 +4,17 @@

#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_plane_helper.h>

#include "vkms_drv.h"
#include "vkms_formats.h"

static const u32 vkms_formats[] = {
- DRM_FORMAT_XRGB8888,
- DRM_FORMAT_XRGB16161616,
- DRM_FORMAT_RGB565
-};
-
-static const u32 vkms_plane_formats[] = {
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_XRGB16161616,
@@ -70,6 +67,20 @@ static void vkms_plane_destroy_state(struct drm_plane *plane,
kfree(vkms_state);
}

+static void vkms_plane_destroy(struct drm_plane *plane)
+{
+ struct vkms_plane *vkms_plane =
+ container_of(plane, struct vkms_plane, base);
+
+ if (plane->state) {
+ vkms_plane_destroy_state(plane, plane->state);
+ plane->state = NULL;
+ }
+
+ drm_plane_cleanup(plane);
+ memset(vkms_plane, 0, sizeof(struct vkms_plane));
+}
+
static void vkms_plane_reset(struct drm_plane *plane)
{
struct vkms_plane_state *vkms_state;
@@ -89,11 +100,12 @@ static void vkms_plane_reset(struct drm_plane *plane)
}

static const struct drm_plane_funcs vkms_plane_funcs = {
- .update_plane = drm_atomic_helper_update_plane,
- .disable_plane = drm_atomic_helper_disable_plane,
- .reset = vkms_plane_reset,
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = vkms_plane_destroy,
+ .reset = vkms_plane_reset,
.atomic_duplicate_state = vkms_plane_duplicate_state,
- .atomic_destroy_state = vkms_plane_destroy_state,
+ .atomic_destroy_state = vkms_plane_destroy_state,
};

static void vkms_plane_atomic_update(struct drm_plane *plane,
@@ -117,13 +129,23 @@ static void vkms_plane_atomic_update(struct drm_plane *plane,
frame_info = vkms_plane_state->frame_info;
memcpy(&frame_info->src, &new_state->src, sizeof(struct drm_rect));
memcpy(&frame_info->dst, &new_state->dst, sizeof(struct drm_rect));
+ memcpy(&frame_info->rotated, &new_state->dst, sizeof(struct drm_rect));
frame_info->fb = fb;
memcpy(&frame_info->map, &shadow_plane_state->data, sizeof(frame_info->map));
drm_framebuffer_get(frame_info->fb);
+ frame_info->rotation = drm_rotation_simplify(new_state->rotation, DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y);
+
+ drm_rect_rotate(&frame_info->rotated, drm_rect_width(&frame_info->rotated),
+ drm_rect_height(&frame_info->rotated), frame_info->rotation);
+
frame_info->offset = fb->offsets[0];
frame_info->pitch = fb->pitches[0];
frame_info->cpp = fb->format->cpp[0];
- vkms_plane_state->plane_read = get_frame_to_line_function(fmt);
+ vkms_plane_state->pixel_read = get_pixel_conversion_function(fmt);
}

static int vkms_plane_atomic_check(struct drm_plane *plane,
@@ -132,7 +154,6 @@ static int vkms_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct drm_crtc_state *crtc_state;
- bool can_position = false;
int ret;

if (!new_plane_state->fb || WARN_ON(!new_plane_state->crtc))
@@ -143,20 +164,13 @@ static int vkms_plane_atomic_check(struct drm_plane *plane,
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);

- if (plane->type != DRM_PLANE_TYPE_PRIMARY)
- can_position = true;
-
ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
- can_position, true);
+ true, true);
if (ret != 0)
return ret;

- /* for now primary plane must be visible and full screen */
- if (!new_plane_state->visible && !can_position)
- return -EINVAL;
-
return 0;
}

@@ -193,7 +207,7 @@ static void vkms_cleanup_fb(struct drm_plane *plane,
drm_gem_fb_vunmap(fb, shadow_plane_state->map);
}

-static const struct drm_plane_helper_funcs vkms_primary_helper_funcs = {
+static const struct drm_plane_helper_funcs vkms_plane_helper_funcs = {
.atomic_update = vkms_plane_atomic_update,
.atomic_check = vkms_plane_atomic_check,
.prepare_fb = vkms_prepare_fb,
@@ -201,41 +215,27 @@ static const struct drm_plane_helper_funcs vkms_primary_helper_funcs = {
};

struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
- enum drm_plane_type type, int index)
+ enum drm_plane_type type)
{
struct drm_device *dev = &vkmsdev->drm;
- const struct drm_plane_helper_funcs *funcs;
+ struct vkms_output *output = &vkmsdev->output;
struct vkms_plane *plane;
- const u32 *formats;
- int nformats;
-
- switch (type) {
- case DRM_PLANE_TYPE_PRIMARY:
- formats = vkms_formats;
- nformats = ARRAY_SIZE(vkms_formats);
- funcs = &vkms_primary_helper_funcs;
- break;
- case DRM_PLANE_TYPE_CURSOR:
- case DRM_PLANE_TYPE_OVERLAY:
- formats = vkms_plane_formats;
- nformats = ARRAY_SIZE(vkms_plane_formats);
- funcs = &vkms_primary_helper_funcs;
- break;
- default:
- formats = vkms_formats;
- nformats = ARRAY_SIZE(vkms_formats);
- funcs = &vkms_primary_helper_funcs;
- break;
- }
+ int ret;
+
+ if (output->num_planes >= VKMS_MAX_PLANES)
+ return ERR_PTR(-ENOMEM);
+
+ plane = &output->planes[output->num_planes++];
+ ret = drm_universal_plane_init(dev, &plane->base, 0, &vkms_plane_funcs,
+ vkms_formats, ARRAY_SIZE(vkms_formats),
+ NULL, type, NULL);
+ if (ret)
+ return ERR_PTR(ret);

- plane = drmm_universal_plane_alloc(dev, struct vkms_plane, base, 1 << index,
- &vkms_plane_funcs,
- formats, nformats,
- NULL, type, NULL);
- if (IS_ERR(plane))
- return plane;
+ drm_plane_helper_add(&plane->base, &vkms_plane_helper_funcs);

- drm_plane_helper_add(&plane->base, funcs);
+ drm_plane_create_rotation_property(&plane->base, DRM_MODE_ROTATE_0,
+ DRM_MODE_ROTATE_MASK | DRM_MODE_REFLECT_MASK);

return plane;
}
diff --git a/drivers/gpu/drm/vkms/vkms_writeback.c b/drivers/gpu/drm/vkms/vkms_writeback.c
index 84a51cd281b9..47449979c564 100644
--- a/drivers/gpu/drm/vkms/vkms_writeback.c
+++ b/drivers/gpu/drm/vkms/vkms_writeback.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+

#include <linux/iosys-map.h>
+#include <linux/kernel.h>

#include <drm/drm_atomic.h>
#include <drm/drm_edid.h>
@@ -15,6 +16,7 @@
#include "vkms_formats.h"

static const u32 vkms_wb_formats[] = {
+ DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_XRGB16161616,
DRM_FORMAT_ARGB16161616,
@@ -101,7 +103,8 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,
struct drm_writeback_job *job)
{
struct vkms_writeback_job *vkmsjob = job->priv;
- struct vkms_device *vkmsdev;
+ struct vkms_crtc *vkms_crtc =
+ container_of(connector, struct vkms_crtc, wb_connector);

if (!job->fb)
return;
@@ -110,8 +113,7 @@ static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector,

drm_framebuffer_put(vkmsjob->wb_frame_info.fb);

- vkmsdev = drm_device_to_vkms_device(job->fb->dev);
- vkms_set_composer(&vkmsdev->output, false);
+ vkms_set_composer(vkms_crtc, false);
kfree(vkmsjob);
}

@@ -120,11 +122,11 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
{
struct drm_connector_state *connector_state = drm_atomic_get_new_connector_state(state,
conn);
- struct vkms_device *vkmsdev = drm_device_to_vkms_device(conn->dev);
- struct vkms_output *output = &vkmsdev->output;
- struct drm_writeback_connector *wb_conn = &output->wb_connector;
+ struct vkms_crtc *vkms_crtc =
+ drm_crtc_to_vkms_crtc(connector_state->crtc);
+ struct drm_writeback_connector *wb_conn = &vkms_crtc->wb_connector;
struct drm_connector_state *conn_state = wb_conn->base.state;
- struct vkms_crtc_state *crtc_state = output->composer_state;
+ struct vkms_crtc_state *crtc_state = vkms_crtc->composer_state;
struct drm_framebuffer *fb = connector_state->writeback_job->fb;
u16 crtc_height = crtc_state->base.crtc->mode.vdisplay;
u16 crtc_width = crtc_state->base.crtc->mode.hdisplay;
@@ -135,20 +137,24 @@ static void vkms_wb_atomic_commit(struct drm_connector *conn,
if (!conn_state)
return;

- vkms_set_composer(&vkmsdev->output, true);
+ vkms_set_composer(vkms_crtc, true);

active_wb = conn_state->writeback_job->priv;
wb_frame_info = &active_wb->wb_frame_info;

- spin_lock_irq(&output->composer_lock);
+ spin_lock_irq(&vkms_crtc->composer_lock);
crtc_state->active_writeback = active_wb;
+ crtc_state->wb_pending = true;
+ spin_unlock_irq(&vkms_crtc->composer_lock);
+
wb_frame_info->offset = fb->offsets[0];
wb_frame_info->pitch = fb->pitches[0];
wb_frame_info->cpp = fb->format->cpp[0];
+
crtc_state->wb_pending = true;
- spin_unlock_irq(&output->composer_lock);
+ spin_unlock_irq(&vkms_crtc->composer_lock);
drm_writeback_queue_job(wb_conn, connector_state);
- active_wb->wb_write = get_line_to_frame_function(wb_format);
+ active_wb->pixel_write = get_pixel_write_function(wb_format);
drm_rect_init(&wb_frame_info->src, 0, 0, crtc_width, crtc_height);
drm_rect_init(&wb_frame_info->dst, 0, 0, crtc_width, crtc_height);
}
@@ -160,9 +166,10 @@ static const struct drm_connector_helper_funcs vkms_wb_conn_helper_funcs = {
.atomic_commit = vkms_wb_atomic_commit,
};

-int vkms_enable_writeback_connector(struct vkms_device *vkmsdev)
+int vkms_enable_writeback_connector(struct vkms_device *vkmsdev,
+ struct vkms_crtc *vkms_crtc)
{
- struct drm_writeback_connector *wb = &vkmsdev->output.wb_connector;
+ struct drm_writeback_connector *wb = &vkms_crtc->wb_connector;

drm_connector_helper_add(&wb->base, &vkms_wb_conn_helper_funcs);

diff --git a/include/drm/drm_fixed.h b/include/drm/drm_fixed.h
index 553210c02ee0..6ea339d5de08 100644
--- a/include/drm/drm_fixed.h
+++ b/include/drm/drm_fixed.h
@@ -25,6 +25,7 @@
#ifndef DRM_FIXED_H
#define DRM_FIXED_H

+#include <linux/kernel.h>
#include <linux/math64.h>

typedef union dfixed {
@@ -70,6 +71,7 @@ static inline u32 dfixed_div(fixed20_12 A, fixed20_12 B)
}

#define DRM_FIXED_POINT 32
+#define DRM_FIXED_POINT_HALF 16
#define DRM_FIXED_ONE (1ULL << DRM_FIXED_POINT)
#define DRM_FIXED_DECIMAL_MASK (DRM_FIXED_ONE - 1)
#define DRM_FIXED_DIGITS_MASK (~DRM_FIXED_DECIMAL_MASK)
@@ -86,6 +88,11 @@ static inline int drm_fixp2int(s64 a)
return ((s64)a) >> DRM_FIXED_POINT;
}

+static inline int drm_fixp2int_round(s64 a)
+{
+ return drm_fixp2int(a + (1 << (DRM_FIXED_POINT_HALF - 1)));
+}
+
static inline int drm_fixp2int_ceil(s64 a)
{
if (a > 0)
--
2.41.0.640.ga95def55d0-goog