Linux-HyperV List
 help / color / mirror / Atom feed
From: Thomas Zimmermann <tzimmermann@suse.de>
To: Hamza Mahfooz <hamzamahfooz@linux.microsoft.com>,
	dri-devel@lists.freedesktop.org
Cc: Dexuan Cui <decui@microsoft.com>,
	Saurabh Sengar <ssengar@linux.microsoft.com>,
	Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	Maxime Ripard <mripard@kernel.org>,
	David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
	linux-hyperv@vger.kernel.org
Subject: Re: [PATCH v2] drm/hyperv: add cursor plane support
Date: Thu, 12 Nov 2026 10:52:01 +0100	[thread overview]
Message-ID: <9a952b79-4f77-4ec1-9bcd-6482b392e0f6@suse.de> (raw)
In-Reply-To: <20261111154950.1304813-1-hamzamahfooz@linux.microsoft.com>



Am 11.11.26 um 16:49 schrieb Hamza Mahfooz:
> The Hyper-V synthetic video device lets the guest set the image and the
> position of the mouse pointer, which RDP clients like the VM Connection
> window then draw on their own, wherever their local mouse is. The driver
> only used this to hide the pointer so far, leaving the cursor to be
> drawn into the framebuffer, where it lags behind the mouse.
>
> Add a cursor plane that hands its image to the host instead. As the
> clients draw the pointer at their mouse position, the hotspot needs to
> be known, so set DRIVER_CURSOR_HOTSPOT.
>
> The host takes ARGB8888 images of up to 96x96 pixels, stored bottom-up
> and sent in parts of at most 8 KiB. Fully transparent columns and rows
> at the right and bottom, like the padding of a cursor buffer of the
> maximum size, are left out to keep the messages small. RDP clients use
> straight alpha for pointer images, so only advertise the "Coverage"
> pixel blend mode.
>
> The host shows a small dot for a pointer marked invisible, so a hidden
> cursor is sent as a single, almost transparent pixel instead, like
> hv_drm_hide_hw_ptr() did, which this replaces. An image without any
> visible pixel is sent likewise, as clients may draw it as an opaque box
> otherwise.
>
> Clients connecting to the VM show a default pointer until they are sent
> one, so keep a copy of the cursor and send it again when the host
> reports a feature change as a client connects, and when the CRTC gets
> enabled.
>
> NOTE: The predominant client (i.e. VMConnect) draws translucent pixels
> as opaque ones blended onto black, this is a limitation of the client.
>
> Signed-off-by: Hamza Mahfooz <hamzamahfooz@linux.microsoft.com>

Looks good now.

Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>

> ---
> v2:
> - Test for !plane_state->visible in the cursor's atomic_check().
>    (Thomas)
> - Use drm_dbg() instead of drm_dbg_atomic(). (Thomas)
> - Add a note as to why the hidden cursor pixel's alpha is 1 rather than
>    0. (Thomas)
> - Add a comment to explain why the cursor is sent again from a work
>    item.
> ---
>   drivers/gpu/drm/hyperv/hyperv_drm.h         |  37 +++-
>   drivers/gpu/drm/hyperv/hyperv_drm_drv.c     |  13 +-
>   drivers/gpu/drm/hyperv/hyperv_drm_modeset.c | 170 ++++++++++++++-
>   drivers/gpu/drm/hyperv/hyperv_drm_proto.c   | 217 ++++++++++++++++----
>   4 files changed, 388 insertions(+), 49 deletions(-)
>
> diff --git a/drivers/gpu/drm/hyperv/hyperv_drm.h b/drivers/gpu/drm/hyperv/hyperv_drm.h
> index f4262a37a..df03185a2 100644
> --- a/drivers/gpu/drm/hyperv/hyperv_drm.h
> +++ b/drivers/gpu/drm/hyperv/hyperv_drm.h
> @@ -13,10 +13,38 @@
>   
>   #define VMBUS_MAX_PACKET_SIZE 0x4000
>   
> +#define SYNTHVID_CURSOR_MAX_X 96
> +#define SYNTHVID_CURSOR_MAX_Y 96
> +#define SYNTHVID_CURSOR_ARGB_PIXEL_SIZE 4
> +#define SYNTHVID_CURSOR_MAX_SIZE (SYNTHVID_CURSOR_MAX_X * \
> +	SYNTHVID_CURSOR_MAX_Y * SYNTHVID_CURSOR_ARGB_PIXEL_SIZE)
> +
> +/*
> + * Copy of the cursor last handed to the host, so that it can be sent again
> + * when needed, e.g. after a VM Connection window was opened.
> + */
> +struct hv_drm_cursor {
> +	/* Serializes pointer messages and protects the fields below */
> +	struct mutex lock;
> +	/* The host may not have the cached shape, e.g. after a failed send */
> +	bool shape_stale;
> +	u32 width;
> +	u32 height;
> +	u32 hot_x;
> +	u32 hot_y;
> +	s32 x;
> +	s32 y;
> +	/* ARGB8888, rows of width pixels, stored bottom-up as the host wants */
> +	u8 image[SYNTHVID_CURSOR_MAX_SIZE];
> +	/* Scratch space for building pointer shape messages */
> +	u8 msg_buf[VMBUS_MAX_PACKET_SIZE];
> +};
> +
>   struct hv_drm_device {
>   	/* drm */
>   	struct drm_device dev;
>   	struct drm_plane plane;
> +	struct drm_plane cursor_plane;
>   	struct drm_crtc crtc;
>   	struct drm_encoder encoder;
>   	struct drm_connector connector;
> @@ -40,6 +68,9 @@ struct hv_drm_device {
>   	u8 init_buf[VMBUS_MAX_PACKET_SIZE];
>   	u8 recv_buf[VMBUS_MAX_PACKET_SIZE];
>   
> +	struct hv_drm_cursor *cursor;
> +	struct work_struct cursor_work;
> +
>   	struct hv_device *hdev;
>   };
>   
> @@ -52,8 +83,12 @@ int hv_drm_mode_config_init(struct hv_drm_device *hv);
>   int hv_drm_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp);
>   int hv_drm_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
>   			    u32 w, u32 h, u32 pitch);
> -int hv_drm_hide_hw_ptr(struct hv_device *hdev);
> +int hv_drm_cursor_init(struct hv_drm_device *hv);
> +void hv_drm_cursor_set_hidden(struct hv_drm_cursor *cursor);
> +void hv_drm_update_cursor(struct hv_drm_device *hv, bool shape);
> +void hv_drm_restore_cursor(struct hv_drm_device *hv);
>   int hv_drm_update_dirt(struct hv_device *hdev, struct drm_rect *rect);
>   int hv_drm_connect_vsp(struct hv_device *hdev);
> +void hv_drm_disconnect_vsp(struct hv_device *hdev);
>   
>   #endif
> diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
> index 031206a5b..21e5fcc3e 100644
> --- a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
> +++ b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
> @@ -26,7 +26,8 @@
>   DEFINE_DRM_GEM_FOPS(hv_drm_fops);
>   
>   static struct drm_driver hv_drm_driver = {
> -	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC |
> +			   DRIVER_CURSOR_HOTSPOT,
>   
>   	.name		 = DRIVER_NAME,
>   	.desc		 = DRIVER_DESC,
> @@ -119,6 +120,10 @@ static int hv_drm_vmbus_probe(struct hv_device *hdev,
>   	hv_set_drvdata(hdev, hv);
>   	hv->hdev = hdev;
>   
> +	ret = hv_drm_cursor_init(hv);
> +	if (ret)
> +		goto err_hv_set_drv_data;
> +
>   	ret = hv_drm_connect_vsp(hdev);
>   	if (ret) {
>   		drm_err(dev, "Failed to connect to vmbus.\n");
> @@ -162,7 +167,7 @@ static int hv_drm_vmbus_probe(struct hv_device *hdev,
>   	iounmap(hv->vram);
>   	vmbus_free_mmio(hv->mem->start, hv->fb_size);
>   err_vmbus_close:
> -	vmbus_close(hdev->channel);
> +	hv_drm_disconnect_vsp(hdev);
>   err_hv_set_drv_data:
>   	hv_set_drvdata(hdev, NULL);
>   	return ret;
> @@ -176,7 +181,7 @@ static void hv_drm_vmbus_remove(struct hv_device *hdev)
>   	vmbus_set_skip_unload(false);
>   	drm_dev_unplug(dev);
>   	drm_atomic_helper_shutdown(dev);
> -	vmbus_close(hdev->channel);
> +	hv_drm_disconnect_vsp(hdev);
>   	hv_set_drvdata(hdev, NULL);
>   
>   	iounmap(hv->vram);
> @@ -197,7 +202,7 @@ static int hv_drm_vmbus_suspend(struct hv_device *hdev)
>   	if (ret)
>   		return ret;
>   
> -	vmbus_close(hdev->channel);
> +	hv_drm_disconnect_vsp(hdev);
>   
>   	return 0;
>   }
> diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
> index da399cb95..759952f4f 100644
> --- a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
> +++ b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
> @@ -6,6 +6,7 @@
>   #include <linux/hyperv.h>
>   
>   #include <drm/drm_atomic.h>
> +#include <drm/drm_blend.h>
>   #include <drm/drm_crtc_helper.h>
>   #include <drm/drm_damage_helper.h>
>   #include <drm/drm_drv.h>
> @@ -110,7 +111,7 @@ static void hv_drm_crtc_helper_atomic_enable(struct drm_crtc *crtc,
>   	struct drm_plane_state *plane_state = plane->state;
>   	struct drm_crtc_state *crtc_state = crtc->state;
>   
> -	hv_drm_hide_hw_ptr(hv->hdev);
> +	hv_drm_restore_cursor(hv);
>   	hv_drm_update_situation(hv->hdev, 1,  hv->screen_depth,
>   				crtc_state->mode.hdisplay,
>   				crtc_state->mode.vdisplay,
> @@ -241,6 +242,152 @@ static const struct drm_plane_funcs hv_drm_plane_funcs = {
>   	DRM_PANIC_PLANE_FUNCS,
>   };
>   
> +static const u32 hv_drm_cursor_formats[] = {
> +	DRM_FORMAT_ARGB8888,
> +};
> +
> +static int hv_drm_cursor_atomic_check(struct drm_plane *plane,
> +				      struct drm_atomic_commit *state)
> +{
> +	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
> +	struct drm_crtc *crtc = plane_state->crtc;
> +	struct drm_crtc_state *crtc_state = NULL;
> +	int ret;
> +
> +	if (crtc)
> +		crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> +
> +	ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
> +						  DRM_PLANE_NO_SCALING,
> +						  DRM_PLANE_NO_SCALING,
> +						  true, true);
> +	if (ret)
> +		return ret;
> +
> +	if (!plane_state->visible)
> +		return 0;
> +
> +	if (plane_state->crtc_w > SYNTHVID_CURSOR_MAX_X ||
> +	    plane_state->crtc_h > SYNTHVID_CURSOR_MAX_Y) {
> +		drm_dbg(plane->dev, "Invalid cursor size %ux%u\n",
> +			plane_state->crtc_w, plane_state->crtc_h);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * Copy the cursor image to the cache, bottom-up as the host wants it. Fully
> + * transparent columns and rows at the right and bottom, like the padding of
> + * a cursor buffer of the maximum size, are left out.
> + */
> +static void hv_drm_cursor_load(struct hv_drm_cursor *cursor,
> +			       const struct drm_plane_state *state,
> +			       const struct iosys_map *map)
> +{
> +	size_t pitch = state->fb->pitches[0];
> +	size_t offset = (state->src_y >> 16) * pitch + (state->src_x >> 16) *
> +		SYNTHVID_CURSOR_ARGB_PIXEL_SIZE;
> +	u32 hot_x = clamp_t(s32, state->hotspot_x, 0, state->crtc_w - 1);
> +	u32 hot_y = clamp_t(s32, state->hotspot_y, 0, state->crtc_h - 1);
> +	u32 width = 0, height = 0;
> +	u32 x, y;
> +
> +	for (y = 0; y < state->crtc_h; y++) {
> +		for (x = state->crtc_w; x; x--) {
> +			if (iosys_map_rd(map,
> +					 (offset + y * pitch + x *
> +					  SYNTHVID_CURSOR_ARGB_PIXEL_SIZE - 1),
> +					 u8))
> +				break;
> +		}
> +
> +		if (x) {
> +			width = max(width, x);
> +			height = y + 1;
> +		}
> +	}
> +
> +	if (!width) {
> +		hv_drm_cursor_set_hidden(cursor);
> +		return;
> +	}
> +
> +	/* Keep the hotspot within the image */
> +	width = max(width, hot_x + 1);
> +	height = max(height, hot_y + 1);
> +
> +	for (y = 0; y < height; y++)
> +		iosys_map_memcpy_from((cursor->image + (height - 1 - y) *
> +				       width * SYNTHVID_CURSOR_ARGB_PIXEL_SIZE),
> +				      map, offset + y * pitch,
> +				      width * SYNTHVID_CURSOR_ARGB_PIXEL_SIZE);
> +
> +	cursor->width = width;
> +	cursor->height = height;
> +	cursor->hot_x = hot_x;
> +	cursor->hot_y = hot_y;
> +}
> +
> +static void hv_drm_cursor_hide(struct hv_drm_device *hv)
> +{
> +	struct hv_drm_cursor *cursor = hv->cursor;
> +
> +	scoped_guard(mutex, &cursor->lock) {
> +		hv_drm_cursor_set_hidden(cursor);
> +		hv_drm_update_cursor(hv, true);
> +	}
> +}
> +
> +static void hv_drm_cursor_atomic_update(struct drm_plane *plane,
> +					struct drm_atomic_commit *state)
> +{
> +	struct hv_drm_device *hv = to_hv_drm(plane->dev);
> +	struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane);
> +	struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
> +	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(new_state);
> +	struct hv_drm_cursor *cursor = hv->cursor;
> +	bool shape;
> +
> +	if (!new_state->visible) {
> +		if (old_state->visible)
> +			hv_drm_cursor_hide(hv);
> +		return;
> +	}
> +
> +	shape = !old_state->visible || old_state->fb != new_state->fb ||
> +		old_state->src_x != new_state->src_x ||
> +		old_state->src_y != new_state->src_y ||
> +		old_state->src_w != new_state->src_w ||
> +		old_state->src_h != new_state->src_h ||
> +		old_state->hotspot_x != new_state->hotspot_x ||
> +		old_state->hotspot_y != new_state->hotspot_y;
> +
> +	scoped_guard(mutex, &cursor->lock) {
> +		if (shape)
> +			hv_drm_cursor_load(cursor, new_state,
> +					   &shadow_plane_state->data[0]);
> +
> +		cursor->x = new_state->crtc_x;
> +		cursor->y = new_state->crtc_y;
> +		hv_drm_update_cursor(hv, shape);
> +	}
> +}
> +
> +static void hv_drm_cursor_atomic_disable(struct drm_plane *plane,
> +					 struct drm_atomic_commit *state)
> +{
> +	hv_drm_cursor_hide(to_hv_drm(plane->dev));
> +}
> +
> +static const struct drm_plane_helper_funcs hv_drm_cursor_helper_funcs = {
> +	DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
> +	.atomic_check = hv_drm_cursor_atomic_check,
> +	.atomic_update = hv_drm_cursor_atomic_update,
> +	.atomic_disable = hv_drm_cursor_atomic_disable,
> +};
> +
>   static const struct drm_encoder_funcs hv_drm_simple_encoder_funcs_cleanup = {
>   	.destroy = drm_encoder_cleanup,
>   };
> @@ -250,6 +397,7 @@ static inline int hv_drm_pipe_init(struct hv_drm_device *hv)
>   	struct drm_device *dev = &hv->dev;
>   	struct drm_encoder *encoder = &hv->encoder;
>   	struct drm_plane *plane = &hv->plane;
> +	struct drm_plane *cursor = &hv->cursor_plane;
>   	struct drm_crtc *crtc = &hv->crtc;
>   	struct drm_connector *connector = &hv->connector;
>   	int ret;
> @@ -264,7 +412,23 @@ static inline int hv_drm_pipe_init(struct hv_drm_device *hv)
>   	drm_plane_helper_add(plane, &hv_drm_plane_helper_funcs);
>   	drm_plane_enable_fb_damage_clips(plane);
>   
> -	ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL,
> +	ret = drm_universal_plane_init(dev, cursor, 0,
> +				       &hv_drm_plane_funcs,
> +				       hv_drm_cursor_formats,
> +				       ARRAY_SIZE(hv_drm_cursor_formats),
> +				       hv_drm_modifiers,
> +				       DRM_PLANE_TYPE_CURSOR, NULL);
> +	if (ret)
> +		return ret;
> +	drm_plane_helper_add(cursor, &hv_drm_cursor_helper_funcs);
> +
> +	/* The cursor image reaches the host's clients with straight alpha */
> +	ret = drm_plane_create_blend_mode_property(cursor,
> +						   BIT(DRM_MODE_BLEND_COVERAGE));
> +	if (ret)
> +		return ret;
> +
> +	ret = drm_crtc_init_with_planes(dev, crtc, plane, cursor,
>   					&hv_drm_crtc_funcs, NULL);
>   	if (ret)
>   		return ret;
> @@ -320,6 +484,8 @@ int hv_drm_mode_config_init(struct hv_drm_device *hv)
>   	dev->mode_config.min_height = 0;
>   	dev->mode_config.max_width = hv->screen_width_max;
>   	dev->mode_config.max_height = hv->screen_height_max;
> +	dev->mode_config.cursor_width = SYNTHVID_CURSOR_MAX_X;
> +	dev->mode_config.cursor_height = SYNTHVID_CURSOR_MAX_Y;
>   
>   	dev->mode_config.preferred_depth = hv->screen_depth;
>   	dev->mode_config.prefer_shadow = 0;
> diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
> index 8ddd34833..99e036576 100644
> --- a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
> +++ b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
> @@ -5,6 +5,7 @@
>   
>   #include <linux/hyperv.h>
>   
> +#include <drm/drm_managed.h>
>   #include <drm/drm_print.h>
>   
>   #include "hyperv_drm.h"
> @@ -111,12 +112,9 @@ struct synthvid_pointer_position {
>   	s32 image_y;
>   } __packed;
>   
> -#define SYNTHVID_CURSOR_MAX_X 96
> -#define SYNTHVID_CURSOR_MAX_Y 96
> -#define SYNTHVID_CURSOR_ARGB_PIXEL_SIZE 4
> -#define SYNTHVID_CURSOR_MAX_SIZE (SYNTHVID_CURSOR_MAX_X * \
> -	SYNTHVID_CURSOR_MAX_Y * SYNTHVID_CURSOR_ARGB_PIXEL_SIZE)
>   #define SYNTHVID_CURSOR_COMPLETE (-1)
> +/* Most pixel data the host accepts in one pointer shape message */
> +#define SYNTHVID_CURSOR_MAX_PAYLOAD 8192
>   
>   struct synthvid_pointer_shape {
>   	u8 part_idx;
> @@ -286,47 +284,166 @@ int hv_drm_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
>   }
>   
>   /*
> - * Hyper-V supports a hardware cursor feature. It's not used by Linux VM,
> - * but the Hyper-V host still draws a point as an extra mouse pointer,
> - * which is unwanted, especially when Xorg is running.
> + * The host always shows a mouse pointer: the one set with the pointer shape
> + * and position messages, or else a small dot, which is also what it shows
> + * for a pointer marked invisible. A hidden cursor is therefore a single,
> + * almost transparent pixel.
>    *
> - * Hide the unwanted pointer, by setting msg.ptr_pos.is_visible = 1 and setting
> - * the msg.ptr_shape.data. Note: setting msg.ptr_pos.is_visible to 0 doesn't
> - * work in tests.
> - *
> - * The hv_drm_hide_hw_ptr() is also called in the handler of the
> - * SYNTHVID_FEATURE_CHANGE event, otherwise the host still draws an extra
> - * unwanted mouse pointer after the VM Connection window is closed and reopened.
> + * NOTE: Its alpha, the last byte, is 1 rather than 0, as clients may draw an
> + *       image whose alpha values are all 0 as an opaque one.
>    */
> -int hv_drm_hide_hw_ptr(struct hv_device *hdev)
> +static const u8 hv_drm_cursor_hidden_pixel[SYNTHVID_CURSOR_ARGB_PIXEL_SIZE] = {
> +	0x00, 0x01, 0x01, 0x01
> +};
> +
> +void hv_drm_cursor_set_hidden(struct hv_drm_cursor *cursor)
>   {
> -	struct synthvid_msg msg;
> +	memcpy(cursor->image, hv_drm_cursor_hidden_pixel,
> +	       sizeof(hv_drm_cursor_hidden_pixel));
> +	cursor->width = 1;
> +	cursor->height = 1;
> +	cursor->hot_x = 0;
> +	cursor->hot_y = 0;
> +	cursor->x = 0;
> +	cursor->y = 0;
> +}
>   
> -	memset(&msg, 0, sizeof(struct synthvid_msg));
> -	msg.vid_hdr.type = SYNTHVID_POINTER_POSITION;
> -	msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
> +/*
> + * Pointer shapes too large for one message are sent in parts numbered from
> + * 0, with SYNTHVID_CURSOR_COMPLETE marking the last part. A shape that fits
> + * in one message is sent as a lone SYNTHVID_CURSOR_COMPLETE part.
> + */
> +static int hv_drm_send_ptr_shape(struct hv_drm_device *hv, u32 width,
> +				 u32 height, u32 hot_x, u32 hot_y,
> +				 const u8 *image)
> +{
> +	const size_t data_offset = offsetof(struct synthvid_msg,
> +					    ptr_shape.data);
> +	size_t size = width * height * SYNTHVID_CURSOR_ARGB_PIXEL_SIZE;
> +	u8 *buf = hv->cursor->msg_buf;
> +	struct synthvid_msg *msg;
> +	size_t offset = 0;
> +	u8 part = 0;
> +	size_t len;
> +	int ret;
> +
> +	BUILD_BUG_ON((offsetof(struct synthvid_msg, ptr_shape.data) +
> +		     SYNTHVID_CURSOR_MAX_PAYLOAD) >
> +		     sizeof_field(struct hv_drm_cursor, msg_buf));
> +
> +	msg = (struct synthvid_msg *)buf;
> +
> +	do {
> +		len = min(size - offset, SYNTHVID_CURSOR_MAX_PAYLOAD);
> +
> +		memset(buf, 0, data_offset);
> +		msg->vid_hdr.type = SYNTHVID_POINTER_SHAPE;
> +		msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
> +			offsetof(struct synthvid_pointer_shape, data) + len;
> +		if (offset + len < size)
> +			msg->ptr_shape.part_idx = part++;
> +		else
> +			msg->ptr_shape.part_idx = SYNTHVID_CURSOR_COMPLETE;
> +		msg->ptr_shape.is_argb = 1;
> +		msg->ptr_shape.width = width;
> +		msg->ptr_shape.height = height;
> +		msg->ptr_shape.hot_x = hot_x;
> +		msg->ptr_shape.hot_y = hot_y;
> +		memcpy(buf + data_offset, image + offset, len);
> +
> +		ret = hv_drm_sendpacket(hv->hdev, msg);
> +		if (ret)
> +			return ret;
> +
> +		offset += len;
> +	} while (offset < size);
> +
> +	return 0;
> +}
> +
> +static int hv_drm_send_ptr_position(struct hv_drm_device *hv, s32 x, s32 y)
> +{
> +	struct synthvid_msg *msg = (struct synthvid_msg *)hv->cursor->msg_buf;
> +
> +	memset(msg, 0, sizeof(struct synthvid_msg));
> +	msg->vid_hdr.type = SYNTHVID_POINTER_POSITION;
> +	msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
>   		sizeof(struct synthvid_pointer_position);
> -	msg.ptr_pos.is_visible = 1;
> -	msg.ptr_pos.video_output = 0;
> -	msg.ptr_pos.image_x = 0;
> -	msg.ptr_pos.image_y = 0;
> -	hv_drm_sendpacket(hdev, &msg);
> +	msg->ptr_pos.is_visible = 1;
> +	msg->ptr_pos.video_output = 0;
> +	msg->ptr_pos.image_x = x;
> +	msg->ptr_pos.image_y = y;
>   
> -	memset(&msg, 0, sizeof(struct synthvid_msg));
> -	msg.vid_hdr.type = SYNTHVID_POINTER_SHAPE;
> -	msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
> -		sizeof(struct synthvid_pointer_shape);
> -	msg.ptr_shape.part_idx = SYNTHVID_CURSOR_COMPLETE;
> -	msg.ptr_shape.is_argb = 1;
> -	msg.ptr_shape.width = 1;
> -	msg.ptr_shape.height = 1;
> -	msg.ptr_shape.hot_x = 0;
> -	msg.ptr_shape.hot_y = 0;
> -	msg.ptr_shape.data[0] = 0;
> -	msg.ptr_shape.data[1] = 1;
> -	msg.ptr_shape.data[2] = 1;
> -	msg.ptr_shape.data[3] = 1;
> -	hv_drm_sendpacket(hdev, &msg);
> +	return hv_drm_sendpacket(hv->hdev, msg);
> +}
> +
> +/*
> + * Send the cached cursor to the host: the shape if @shape is set or the host
> + * may lack it, then the position. The position goes last as the host applies
> + * the hotspot of its current shape to it.
> + */
> +void hv_drm_update_cursor(struct hv_drm_device *hv, bool shape)
> +{
> +	struct hv_drm_cursor *cursor = hv->cursor;
> +	int ret = 0;
> +
> +	lockdep_assert_held(&cursor->lock);
> +
> +	if (shape || cursor->shape_stale) {
> +		/*
> +		 * The host may still be waiting for the rest of a multi-part
> +		 * shape, and rejects new shapes until it gets a final part.
> +		 * A one pixel shape completes any such shape, and is a valid
> +		 * shape of its own otherwise.
> +		 */
> +		if (cursor->shape_stale)
> +			ret = hv_drm_send_ptr_shape(hv, 1, 1, 0, 0,
> +						    hv_drm_cursor_hidden_pixel);
> +		if (!ret)
> +			ret = hv_drm_send_ptr_shape(hv, cursor->width,
> +						    cursor->height,
> +						    cursor->hot_x,
> +						    cursor->hot_y,
> +						    cursor->image);
> +		cursor->shape_stale = ret;
> +	}
> +
> +	hv_drm_send_ptr_position(hv, cursor->x, cursor->y);
> +}
> +
> +void hv_drm_restore_cursor(struct hv_drm_device *hv)
> +{
> +	scoped_guard(mutex, &hv->cursor->lock)
> +		hv_drm_update_cursor(hv, true);
> +}
> +
> +static void hv_drm_restore_cursor_work(struct work_struct *work)
> +{
> +	struct hv_drm_device *hv = container_of(work, struct hv_drm_device,
> +						cursor_work);
> +
> +	hv_drm_restore_cursor(hv);
> +}
> +
> +int hv_drm_cursor_init(struct hv_drm_device *hv)
> +{
> +	struct hv_drm_cursor *cursor;
> +	int ret;
> +
> +	cursor = drmm_kzalloc(&hv->dev, sizeof(*cursor), GFP_KERNEL);
> +	if (!cursor)
> +		return -ENOMEM;
> +
> +	ret = drmm_mutex_init(&hv->dev, &cursor->lock);
> +	if (ret)
> +		return ret;
> +
> +	hv_drm_cursor_set_hidden(cursor);
> +	cursor->shape_stale = true;
> +	hv->cursor = cursor;
> +
> +	INIT_WORK(&hv->cursor_work, hv_drm_restore_cursor_work);
> +	disable_work(&hv->cursor_work);
>   
>   	return 0;
>   }
> @@ -473,8 +590,13 @@ static void hv_drm_receive_sub(struct hv_device *hdev, u32 bytes_recvd)
>   			return;
>   		}
>   		hv->dirt_needed = msg->feature_chg.is_dirt_needed;
> +		/*
> +		 * Send the cursor to clients that may have just connected, from
> +		 * a work item, as that takes the cursor's mutex and the channel
> +		 * callback runs in atomic context.
> +		 */
>   		if (hv->dirt_needed)
> -			hv_drm_hide_hw_ptr(hv->hdev);
> +			schedule_work(&hv->cursor_work);
>   		return;
>   	default:
>   		return;
> @@ -538,9 +660,11 @@ int hv_drm_connect_vsp(struct hv_device *hdev)
>   	struct drm_device *dev = &hv->dev;
>   	int ret;
>   
> +	enable_work(&hv->cursor_work);
>   	ret = vmbus_open(hdev->channel, VMBUS_RING_BUFSIZE, VMBUS_RING_BUFSIZE,
>   			 NULL, 0, hv_drm_receive, hdev);
>   	if (ret) {
> +		disable_work_sync(&hv->cursor_work);
>   		drm_err(dev, "Unable to open vmbus channel\n");
>   		return ret;
>   	}
> @@ -574,6 +698,15 @@ int hv_drm_connect_vsp(struct hv_device *hdev)
>   	return 0;
>   
>   error:
> -	vmbus_close(hdev->channel);
> +	hv_drm_disconnect_vsp(hdev);
>   	return ret;
>   }
> +
> +void hv_drm_disconnect_vsp(struct hv_device *hdev)
> +{
> +	struct hv_drm_device *hv = hv_get_drvdata(hdev);
> +
> +	/* Stop the work before vmbus_close() frees the ring buffers */
> +	disable_work_sync(&hv->cursor_work);
> +	vmbus_close(hdev->channel);
> +}

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg)



  reply	other threads:[~2026-11-12  9:52 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-11 15:49 [PATCH v2] drm/hyperv: add cursor plane support Hamza Mahfooz
2026-11-12  9:52 ` Thomas Zimmermann [this message]
2026-11-12 12:46 ` Hamza Mahfooz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9a952b79-4f77-4ec1-9bcd-6482b392e0f6@suse.de \
    --to=tzimmermann@suse.de \
    --cc=airlied@gmail.com \
    --cc=decui@microsoft.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=hamzamahfooz@linux.microsoft.com \
    --cc=linux-hyperv@vger.kernel.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=simona@ffwll.ch \
    --cc=ssengar@linux.microsoft.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox