* [PATCH 1/2] drm/hyperv: add modes for all resolutions offered by the host
2026-11-13 21:58 [PATCH 0/2] drm/hyperv: address worthwhile gaps Hamza Mahfooz
@ 2026-11-13 21:58 ` Hamza Mahfooz
2026-11-13 21:58 ` [PATCH 2/2] drm/hyperv: batch dirty rectangles Hamza Mahfooz
1 sibling, 0 replies; 3+ messages in thread
From: Hamza Mahfooz @ 2026-11-13 21:58 UTC (permalink / raw)
To: dri-devel
Cc: Dexuan Cui, Saurabh Sengar, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Kees Cook,
Gustavo A. R. Silva, linux-hyperv, linux-hardening,
Michael Kelley, Hamza Mahfooz
The host reports the resolutions it supports, which depend on the VM's
configuration: a list of standard resolutions by default, those up to a
maximum plus the maximum itself, or a single resolution. The driver
only takes the largest and the default resolution from the list, and
offers the DMT modes of up to 60 Hz that fit. Resolutions without such
a mode are thus missing (e.g. 1152x864, for which DMT only has a 75 Hz
mode). A VM set to 2560x1440 even ends up without a preferred mode, as
DMT has no 2560x1440 mode at all.
So, keep the list and add a 60 Hz CVT mode for every resolution in it
that lacks a mode of its size, so that all of them can be used. CVT
rounds the width down to a multiple of 8 while the host only requires an
even width (e.g. 1366x768), so restore the width afterwards.
Signed-off-by: Hamza Mahfooz <hamzamahfooz@linux.microsoft.com>
---
drivers/gpu/drm/hyperv/hyperv_drm.h | 9 +++++++
drivers/gpu/drm/hyperv/hyperv_drm_modeset.c | 29 +++++++++++++++++++++
drivers/gpu/drm/hyperv/hyperv_drm_proto.c | 10 +++----
3 files changed, 42 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm.h b/drivers/gpu/drm/hyperv/hyperv_drm.h
index df03185a2e19..6006d665343b 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm.h
+++ b/drivers/gpu/drm/hyperv/hyperv_drm.h
@@ -19,6 +19,13 @@
#define SYNTHVID_CURSOR_MAX_SIZE (SYNTHVID_CURSOR_MAX_X * \
SYNTHVID_CURSOR_MAX_Y * SYNTHVID_CURSOR_ARGB_PIXEL_SIZE)
+#define SYNTHVID_MAX_RESOLUTION_COUNT 64
+
+struct hvd_screen_info {
+ u16 width;
+ u16 height;
+} __packed;
+
/*
* 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.
@@ -55,6 +62,8 @@ struct hv_drm_device {
u32 preferred_width;
u32 preferred_height;
u32 screen_depth;
+ struct hvd_screen_info resolutions[SYNTHVID_MAX_RESOLUTION_COUNT];
+ unsigned int resolution_count;
/* hw */
struct resource *mem;
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
index 48d7751c0bdd..177f804dca0c 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
+++ b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
@@ -46,14 +46,43 @@ static int hv_drm_blit_to_vram_rect(struct drm_framebuffer *fb,
return 0;
}
+static int hv_drm_connector_add_mode(struct drm_connector *connector,
+ u16 width, u16 height)
+{
+ struct drm_display_mode *mode;
+
+ list_for_each_entry(mode, &connector->probed_modes, head) {
+ if (mode->hdisplay == width && mode->vdisplay == height)
+ return 0;
+ }
+
+ mode = drm_cvt_mode(connector->dev, width, height, 60, false, false,
+ false);
+ if (!mode)
+ return 0;
+
+ /* CVT rounds the width down to a multiple of 8, the host does not */
+ mode->hdisplay = width;
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
static int hv_drm_connector_get_modes(struct drm_connector *connector)
{
struct hv_drm_device *hv = to_hv_drm(connector->dev);
+ unsigned int i;
int count;
count = drm_add_modes_noedid(connector,
connector->dev->mode_config.max_width,
connector->dev->mode_config.max_height);
+ for (i = 0; i < hv->resolution_count; i++)
+ count += hv_drm_connector_add_mode(connector,
+ hv->resolutions[i].width,
+ hv->resolutions[i].height);
drm_set_preferred_mode(connector, hv->preferred_width,
hv->preferred_height);
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
index 99e036576929..81ffb88ba6a1 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
+++ b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
@@ -56,11 +56,6 @@ struct pipe_msg_hdr {
u32 size; /* size of message after this field */
} __packed;
-struct hvd_screen_info {
- u16 width;
- u16 height;
-} __packed;
-
struct synthvid_msg_hdr {
u32 type;
u32 size; /* size of this header + payload after this field */
@@ -145,7 +140,6 @@ struct synthvid_dirt {
} __packed;
#define SYNTHVID_EDID_BLOCK_SIZE 128
-#define SYNTHVID_MAX_RESOLUTION_COUNT 64
struct synthvid_supported_resolution_req {
u8 maximum_resolution_count;
@@ -522,6 +516,10 @@ static int hv_drm_get_supported_resolution(struct hv_device *hdev)
hv->preferred_height =
msg->resolution_resp.supported_resolution[index].height;
+ hv->resolution_count = msg->resolution_resp.resolution_count;
+ memcpy(hv->resolutions, msg->resolution_resp.supported_resolution,
+ hv->resolution_count * sizeof(hv->resolutions[0]));
+
return 0;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH 2/2] drm/hyperv: batch dirty rectangles
2026-11-13 21:58 [PATCH 0/2] drm/hyperv: address worthwhile gaps Hamza Mahfooz
2026-11-13 21:58 ` [PATCH 1/2] drm/hyperv: add modes for all resolutions offered by the host Hamza Mahfooz
@ 2026-11-13 21:58 ` Hamza Mahfooz
1 sibling, 0 replies; 3+ messages in thread
From: Hamza Mahfooz @ 2026-11-13 21:58 UTC (permalink / raw)
To: dri-devel
Cc: Dexuan Cui, Saurabh Sengar, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Kees Cook,
Gustavo A. R. Silva, linux-hyperv, linux-hardening,
Michael Kelley, Hamza Mahfooz
Every damage clip of a primary plane update is sent to the host in a
dirt message of its own, although a dirt message can hold up to 255
rectangles and updates may come with many clips, e.g. up to 256 from
the DIRTYFB ioctl.
So, collect the clips of an update in one message instead, and send it
once all of them were copied to VRAM, or earlier whenever it is full.
The panic handler keeps sending a message of its own, as it may
interrupt a plane update.
Signed-off-by: Hamza Mahfooz <hamzamahfooz@linux.microsoft.com>
---
drivers/gpu/drm/hyperv/hyperv_drm.h | 6 ++
drivers/gpu/drm/hyperv/hyperv_drm_drv.c | 4 ++
drivers/gpu/drm/hyperv/hyperv_drm_modeset.c | 3 +-
drivers/gpu/drm/hyperv/hyperv_drm_proto.c | 69 +++++++++++++++++----
4 files changed, 68 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm.h b/drivers/gpu/drm/hyperv/hyperv_drm.h
index 6006d665343b..c17f06f18232 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm.h
+++ b/drivers/gpu/drm/hyperv/hyperv_drm.h
@@ -47,6 +47,8 @@ struct hv_drm_cursor {
u8 msg_buf[VMBUS_MAX_PACKET_SIZE];
};
+struct synthvid_msg;
+
struct hv_drm_device {
/* drm */
struct drm_device dev;
@@ -73,6 +75,7 @@ struct hv_drm_device {
struct completion wait;
u32 mmio_megabytes;
bool dirt_needed;
+ struct synthvid_msg *dirt_msg;
u8 init_buf[VMBUS_MAX_PACKET_SIZE];
u8 recv_buf[VMBUS_MAX_PACKET_SIZE];
@@ -96,6 +99,9 @@ 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_dirt_init(struct hv_drm_device *hv);
+void hv_drm_flush_dirt(struct hv_drm_device *hv);
+void hv_drm_add_dirt(struct hv_drm_device *hv, const struct drm_rect *rect);
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);
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
index 21e5fcc3eb68..56995eaec4d1 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
+++ b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
@@ -124,6 +124,10 @@ static int hv_drm_vmbus_probe(struct hv_device *hdev,
if (ret)
goto err_hv_set_drv_data;
+ ret = hv_drm_dirt_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");
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
index 177f804dca0c..b04b0a30a1c1 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
+++ b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
@@ -217,8 +217,9 @@ static void hv_drm_plane_atomic_update(struct drm_plane *plane,
continue;
hv_drm_blit_to_vram_rect(new_state->fb, &shadow_plane_state->data[0], &damage);
- hv_drm_update_dirt(hv->hdev, &damage);
+ hv_drm_add_dirt(hv, &damage);
}
+ hv_drm_flush_dirt(hv);
}
static int hv_drm_plane_get_scanout_buffer(struct drm_plane *plane,
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
index 81ffb88ba6a1..1f35a32858e4 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
+++ b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
@@ -133,10 +133,12 @@ struct rect {
s32 x2, y2; /* bottom right corner, exclusive */
} __packed;
+#define SYNTHVID_MAX_DIRT_COUNT 255
+
struct synthvid_dirt {
u8 video_output;
u8 dirt_count;
- struct rect rect[1];
+ struct rect rect[] __counted_by(dirt_count);
} __packed;
#define SYNTHVID_EDID_BLOCK_SIZE 128
@@ -442,6 +444,57 @@ int hv_drm_cursor_init(struct hv_drm_device *hv)
return 0;
}
+int hv_drm_dirt_init(struct hv_drm_device *hv)
+{
+ hv->dirt_msg = drmm_kzalloc(&hv->dev,
+ offsetof(struct synthvid_msg, dirt) +
+ struct_size_t(struct synthvid_dirt, rect,
+ SYNTHVID_MAX_DIRT_COUNT),
+ GFP_KERNEL);
+
+ return hv->dirt_msg ? 0 : -ENOMEM;
+}
+
+static void hv_drm_add_dirt_rect(struct synthvid_dirt *dirt,
+ const struct drm_rect *rect)
+{
+ u8 idx = dirt->dirt_count++;
+ struct rect *r = &dirt->rect[idx];
+
+ r->x1 = rect->x1;
+ r->y1 = rect->y1;
+ r->x2 = rect->x2;
+ r->y2 = rect->y2;
+}
+
+static int hv_drm_send_dirt(struct hv_device *hdev, struct synthvid_msg *msg)
+{
+ msg->vid_hdr.type = SYNTHVID_DIRT;
+ msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
+ struct_size(&msg->dirt, rect, msg->dirt.dirt_count);
+ msg->dirt.video_output = 0;
+
+ return hv_drm_sendpacket(hdev, msg);
+}
+
+void hv_drm_flush_dirt(struct hv_drm_device *hv)
+{
+ struct synthvid_msg *msg = hv->dirt_msg;
+
+ if (msg->dirt.dirt_count && hv->dirt_needed)
+ hv_drm_send_dirt(hv->hdev, msg);
+
+ msg->dirt.dirt_count = 0;
+}
+
+void hv_drm_add_dirt(struct hv_drm_device *hv, const struct drm_rect *rect)
+{
+ if (hv->dirt_msg->dirt.dirt_count == SYNTHVID_MAX_DIRT_COUNT)
+ hv_drm_flush_dirt(hv);
+
+ hv_drm_add_dirt_rect(&hv->dirt_msg->dirt, rect);
+}
+
int hv_drm_update_dirt(struct hv_device *hdev, struct drm_rect *rect)
{
struct hv_drm_device *hv = hv_get_drvdata(hdev);
@@ -451,18 +504,8 @@ int hv_drm_update_dirt(struct hv_device *hdev, struct drm_rect *rect)
return 0;
memset(&msg, 0, sizeof(struct synthvid_msg));
-
- msg.vid_hdr.type = SYNTHVID_DIRT;
- msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
- sizeof(struct synthvid_dirt);
- msg.dirt.video_output = 0;
- msg.dirt.dirt_count = 1;
- msg.dirt.rect[0].x1 = rect->x1;
- msg.dirt.rect[0].y1 = rect->y1;
- msg.dirt.rect[0].x2 = rect->x2;
- msg.dirt.rect[0].y2 = rect->y2;
-
- hv_drm_sendpacket(hdev, &msg);
+ hv_drm_add_dirt_rect(&msg.dirt, rect);
+ hv_drm_send_dirt(hdev, &msg);
return 0;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 3+ messages in thread