| 1 | |
| 2 | |
| 3 | * Copyright 2021 Microsoft
|
| 4 |
|
| 5 |
|
| 6 | #include <linux/hyperv.h>
|
| 7 |
|
| 8 | #include <drm/drm_print.h>
|
| 9 | #include <drm/drm_simple_kms_helper.h>
|
| 10 |
|
| 11 | #include "hyperv_drm.h"
|
| 12 |
|
| 13 | #define VMBUS_RING_BUFSIZE VMBUS_RING_SIZE(256 * 1024)
|
| 14 | #define VMBUS_VSP_TIMEOUT (10 * HZ)
|
| 15 |
|
| 16 | #define SYNTHVID_VERSION(major, minor) ((minor) << 16 | (major))
|
| 17 | #define SYNTHVID_VER_GET_MAJOR(ver) (ver & 0x0000ffff)
|
| 18 | #define SYNTHVID_VER_GET_MINOR(ver) ((ver & 0xffff0000) >> 16)
|
| 19 |
|
| 20 |
|
| 21 | #define SYNTHVID_VERSION_WIN7 SYNTHVID_VERSION(3, 0)
|
| 22 | #define SYNTHVID_VERSION_WIN8 SYNTHVID_VERSION(3, 2)
|
| 23 | #define SYNTHVID_VERSION_WIN10 SYNTHVID_VERSION(3, 5)
|
| 24 |
|
| 25 | #define SYNTHVID_DEPTH_WIN8 32
|
| 26 | #define SYNTHVID_WIDTH_WIN8 1600
|
| 27 | #define SYNTHVID_HEIGHT_WIN8 1200
|
| 28 | #define SYNTHVID_FB_SIZE_WIN8 (8 * 1024 * 1024)
|
| 29 |
|
| 30 | enum pipe_msg_type {
|
| 31 | PIPE_MSG_INVALID,
|
| 32 | PIPE_MSG_DATA,
|
| 33 | PIPE_MSG_MAX
|
| 34 | };
|
| 35 |
|
| 36 | enum synthvid_msg_type {
|
| 37 | SYNTHVID_ERROR = 0,
|
| 38 | SYNTHVID_VERSION_REQUEST = 1,
|
| 39 | SYNTHVID_VERSION_RESPONSE = 2,
|
| 40 | SYNTHVID_VRAM_LOCATION = 3,
|
| 41 | SYNTHVID_VRAM_LOCATION_ACK = 4,
|
| 42 | SYNTHVID_SITUATION_UPDATE = 5,
|
| 43 | SYNTHVID_SITUATION_UPDATE_ACK = 6,
|
| 44 | SYNTHVID_POINTER_POSITION = 7,
|
| 45 | SYNTHVID_POINTER_SHAPE = 8,
|
| 46 | SYNTHVID_FEATURE_CHANGE = 9,
|
| 47 | SYNTHVID_DIRT = 10,
|
| 48 | SYNTHVID_RESOLUTION_REQUEST = 13,
|
| 49 | SYNTHVID_RESOLUTION_RESPONSE = 14,
|
| 50 |
|
| 51 | SYNTHVID_MAX = 15
|
| 52 | };
|
| 53 |
|
| 54 | struct pipe_msg_hdr {
|
| 55 | u32 type;
|
| 56 | u32 size;
|
| 57 | } __packed;
|
| 58 |
|
| 59 | struct hvd_screen_info {
|
| 60 | u16 width;
|
| 61 | u16 height;
|
| 62 | } __packed;
|
| 63 |
|
| 64 | struct synthvid_msg_hdr {
|
| 65 | u32 type;
|
| 66 | u32 size;
|
| 67 | } __packed;
|
| 68 |
|
| 69 | struct synthvid_version_req {
|
| 70 | u32 version;
|
| 71 | } __packed;
|
| 72 |
|
| 73 | struct synthvid_version_resp {
|
| 74 | u32 version;
|
| 75 | u8 is_accepted;
|
| 76 | u8 max_video_outputs;
|
| 77 | } __packed;
|
| 78 |
|
| 79 | struct synthvid_vram_location {
|
| 80 | u64 user_ctx;
|
| 81 | u8 is_vram_gpa_specified;
|
| 82 | u64 vram_gpa;
|
| 83 | } __packed;
|
| 84 |
|
| 85 | struct synthvid_vram_location_ack {
|
| 86 | u64 user_ctx;
|
| 87 | } __packed;
|
| 88 |
|
| 89 | struct video_output_situation {
|
| 90 | u8 active;
|
| 91 | u32 vram_offset;
|
| 92 | u8 depth_bits;
|
| 93 | u32 width_pixels;
|
| 94 | u32 height_pixels;
|
| 95 | u32 pitch_bytes;
|
| 96 | } __packed;
|
| 97 |
|
| 98 | struct synthvid_situation_update {
|
| 99 | u64 user_ctx;
|
| 100 | u8 video_output_count;
|
| 101 | struct video_output_situation video_output[1];
|
| 102 | } __packed;
|
| 103 |
|
| 104 | struct synthvid_situation_update_ack {
|
| 105 | u64 user_ctx;
|
| 106 | } __packed;
|
| 107 |
|
| 108 | struct synthvid_pointer_position {
|
| 109 | u8 is_visible;
|
| 110 | u8 video_output;
|
| 111 | s32 image_x;
|
| 112 | s32 image_y;
|
| 113 | } __packed;
|
| 114 |
|
| 115 | #define SYNTHVID_CURSOR_MAX_X 96
|
| 116 | #define SYNTHVID_CURSOR_MAX_Y 96
|
| 117 | #define SYNTHVID_CURSOR_ARGB_PIXEL_SIZE 4
|
| 118 | #define SYNTHVID_CURSOR_MAX_SIZE (SYNTHVID_CURSOR_MAX_X * \
|
| 119 | SYNTHVID_CURSOR_MAX_Y * SYNTHVID_CURSOR_ARGB_PIXEL_SIZE)
|
| 120 | #define SYNTHVID_CURSOR_COMPLETE (-1)
|
| 121 |
|
| 122 | struct synthvid_pointer_shape {
|
| 123 | u8 part_idx;
|
| 124 | u8 is_argb;
|
| 125 | u32 width;
|
| 126 | u32 height;
|
| 127 | u32 hot_x;
|
| 128 | u32 hot_y;
|
| 129 | u8 data[4];
|
| 130 | } __packed;
|
| 131 |
|
| 132 | struct synthvid_feature_change {
|
| 133 | u8 is_dirt_needed;
|
| 134 | u8 is_ptr_pos_needed;
|
| 135 | u8 is_ptr_shape_needed;
|
| 136 | u8 is_situ_needed;
|
| 137 | } __packed;
|
| 138 |
|
| 139 | struct rect {
|
| 140 | s32 x1, y1;
|
| 141 | s32 x2, y2;
|
| 142 | } __packed;
|
| 143 |
|
| 144 | struct synthvid_dirt {
|
| 145 | u8 video_output;
|
| 146 | u8 dirt_count;
|
| 147 | struct rect rect[1];
|
| 148 | } __packed;
|
| 149 |
|
| 150 | #define SYNTHVID_EDID_BLOCK_SIZE 128
|
| 151 | #define SYNTHVID_MAX_RESOLUTION_COUNT 64
|
| 152 |
|
| 153 | struct synthvid_supported_resolution_req {
|
| 154 | u8 maximum_resolution_count;
|
| 155 | } __packed;
|
| 156 |
|
| 157 | struct synthvid_supported_resolution_resp {
|
| 158 | u8 edid_block[SYNTHVID_EDID_BLOCK_SIZE];
|
| 159 | u8 resolution_count;
|
| 160 | u8 default_resolution_index;
|
| 161 | u8 is_standard;
|
| 162 | struct hvd_screen_info supported_resolution[SYNTHVID_MAX_RESOLUTION_COUNT];
|
| 163 | } __packed;
|
| 164 |
|
| 165 | struct synthvid_msg {
|
| 166 | struct pipe_msg_hdr pipe_hdr;
|
| 167 | struct synthvid_msg_hdr vid_hdr;
|
| 168 | union {
|
| 169 | struct synthvid_version_req ver_req;
|
| 170 | struct synthvid_version_resp ver_resp;
|
| 171 | struct synthvid_vram_location vram;
|
| 172 | struct synthvid_vram_location_ack vram_ack;
|
| 173 | struct synthvid_situation_update situ;
|
| 174 | struct synthvid_situation_update_ack situ_ack;
|
| 175 | struct synthvid_pointer_position ptr_pos;
|
| 176 | struct synthvid_pointer_shape ptr_shape;
|
| 177 | struct synthvid_feature_change feature_chg;
|
| 178 | struct synthvid_dirt dirt;
|
| 179 | struct synthvid_supported_resolution_req resolution_req;
|
| 180 | struct synthvid_supported_resolution_resp resolution_resp;
|
| 181 | };
|
| 182 | } __packed;
|
| 183 |
|
| 184 | static inline bool hv_drm_version_ge(u32 ver1, u32 ver2)
|
| 185 | {
|
| 186 | if (SYNTHVID_VER_GET_MAJOR(ver1) > SYNTHVID_VER_GET_MAJOR(ver2) ||
|
| 187 | (SYNTHVID_VER_GET_MAJOR(ver1) == SYNTHVID_VER_GET_MAJOR(ver2) &&
|
| 188 | SYNTHVID_VER_GET_MINOR(ver1) >= SYNTHVID_VER_GET_MINOR(ver2)))
|
| 189 | return true;
|
| 190 |
|
| 191 | return false;
|
| 192 | }
|
| 193 |
|
| 194 | static inline int hv_drm_sendpacket(struct hv_device *hdev, struct synthvid_msg *msg)
|
| 195 | {
|
| 196 | static atomic64_t request_id = ATOMIC64_INIT(0);
|
| 197 | struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
| 198 | int ret;
|
| 199 |
|
| 200 | msg->pipe_hdr.type = PIPE_MSG_DATA;
|
| 201 | msg->pipe_hdr.size = msg->vid_hdr.size;
|
| 202 |
|
| 203 | ret = vmbus_sendpacket(hdev->channel, msg,
|
| 204 | msg->vid_hdr.size + sizeof(struct pipe_msg_hdr),
|
| 205 | atomic64_inc_return(&request_id),
|
| 206 | VM_PKT_DATA_INBAND, 0);
|
| 207 |
|
| 208 | if (ret)
|
| 209 | drm_err_ratelimited(&hv->dev, "Unable to send packet via vmbus; error %d\n", ret);
|
| 210 |
|
| 211 | return ret;
|
| 212 | }
|
| 213 |
|
| 214 | static int hv_drm_negotiate_version(struct hv_device *hdev, u32 ver)
|
| 215 | {
|
| 216 | struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
| 217 | struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf;
|
| 218 | struct drm_device *dev = &hv->dev;
|
| 219 | unsigned long t;
|
| 220 |
|
| 221 | memset(msg, 0, sizeof(struct synthvid_msg));
|
| 222 | msg->vid_hdr.type = SYNTHVID_VERSION_REQUEST;
|
| 223 | msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
|
| 224 | sizeof(struct synthvid_version_req);
|
| 225 | msg->ver_req.version = ver;
|
| 226 | hv_drm_sendpacket(hdev, msg);
|
| 227 |
|
| 228 | t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT);
|
| 229 | if (!t) {
|
| 230 | drm_err(dev, "Time out on waiting version response\n");
|
| 231 | return -ETIMEDOUT;
|
| 232 | }
|
| 233 |
|
| 234 | if (!msg->ver_resp.is_accepted) {
|
| 235 | drm_err(dev, "Version request not accepted\n");
|
| 236 | return -ENODEV;
|
| 237 | }
|
| 238 |
|
| 239 | hv->synthvid_version = ver;
|
| 240 | drm_info(dev, "Synthvid Version major %d, minor %d\n",
|
| 241 | SYNTHVID_VER_GET_MAJOR(ver), SYNTHVID_VER_GET_MINOR(ver));
|
| 242 |
|
| 243 | return 0;
|
| 244 | }
|
| 245 |
|
| 246 | int hv_drm_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp)
|
| 247 | {
|
| 248 | struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
| 249 | struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf;
|
| 250 | struct drm_device *dev = &hv->dev;
|
| 251 | unsigned long t;
|
| 252 |
|
| 253 | memset(msg, 0, sizeof(struct synthvid_msg));
|
| 254 | msg->vid_hdr.type = SYNTHVID_VRAM_LOCATION;
|
| 255 | msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
|
| 256 | sizeof(struct synthvid_vram_location);
|
| 257 | msg->vram.user_ctx = vram_pp;
|
| 258 | msg->vram.vram_gpa = vram_pp;
|
| 259 | msg->vram.is_vram_gpa_specified = 1;
|
| 260 | hv_drm_sendpacket(hdev, msg);
|
| 261 |
|
| 262 | t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT);
|
| 263 | if (!t) {
|
| 264 | drm_err(dev, "Time out on waiting vram location ack\n");
|
| 265 | return -ETIMEDOUT;
|
| 266 | }
|
| 267 | if (msg->vram_ack.user_ctx != vram_pp) {
|
| 268 | drm_err(dev, "Unable to set VRAM location\n");
|
| 269 | return -ENODEV;
|
| 270 | }
|
| 271 |
|
| 272 | return 0;
|
| 273 | }
|
| 274 |
|
| 275 | int hv_drm_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
|
| 276 | u32 w, u32 h, u32 pitch)
|
| 277 | {
|
| 278 | struct synthvid_msg msg;
|
| 279 |
|
| 280 | memset(&msg, 0, sizeof(struct synthvid_msg));
|
| 281 |
|
| 282 | msg.vid_hdr.type = SYNTHVID_SITUATION_UPDATE;
|
| 283 | msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
|
| 284 | sizeof(struct synthvid_situation_update);
|
| 285 | msg.situ.user_ctx = 0;
|
| 286 | msg.situ.video_output_count = 1;
|
| 287 | msg.situ.video_output[0].active = active;
|
| 288 |
|
| 289 | msg.situ.video_output[0].vram_offset = 0;
|
| 290 | msg.situ.video_output[0].depth_bits = bpp;
|
| 291 | msg.situ.video_output[0].width_pixels = w;
|
| 292 | msg.situ.video_output[0].height_pixels = h;
|
| 293 | msg.situ.video_output[0].pitch_bytes = pitch;
|
| 294 |
|
| 295 | hv_drm_sendpacket(hdev, &msg);
|
| 296 |
|
| 297 | return 0;
|
| 298 | }
|
| 299 |
|
| 300 | |
| 301 | * Hyper-V supports a hardware cursor feature. It's not used by Linux VM,
|
| 302 | * but the Hyper-V host still draws a point as an extra mouse pointer,
|
| 303 | * which is unwanted, especially when Xorg is running.
|
| 304 | *
|
| 305 | * Hide the unwanted pointer, by setting msg.ptr_pos.is_visible = 1 and setting
|
| 306 | * the msg.ptr_shape.data. Note: setting msg.ptr_pos.is_visible to 0 doesn't
|
| 307 | * work in tests.
|
| 308 | *
|
| 309 | * The hv_drm_hide_hw_ptr() is also called in the handler of the
|
| 310 | * SYNTHVID_FEATURE_CHANGE event, otherwise the host still draws an extra
|
| 311 | * unwanted mouse pointer after the VM Connection window is closed and reopened.
|
| 312 |
|
| 313 | int hv_drm_hide_hw_ptr(struct hv_device *hdev)
|
| 314 | {
|
| 315 | struct synthvid_msg msg;
|
| 316 |
|
| 317 | memset(&msg, 0, sizeof(struct synthvid_msg));
|
| 318 | msg.vid_hdr.type = SYNTHVID_POINTER_POSITION;
|
| 319 | msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
|
| 320 | sizeof(struct synthvid_pointer_position);
|
| 321 | msg.ptr_pos.is_visible = 1;
|
| 322 | msg.ptr_pos.video_output = 0;
|
| 323 | msg.ptr_pos.image_x = 0;
|
| 324 | msg.ptr_pos.image_y = 0;
|
| 325 | hv_drm_sendpacket(hdev, &msg);
|
| 326 |
|
| 327 | memset(&msg, 0, sizeof(struct synthvid_msg));
|
| 328 | msg.vid_hdr.type = SYNTHVID_POINTER_SHAPE;
|
| 329 | msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
|
| 330 | sizeof(struct synthvid_pointer_shape);
|
| 331 | msg.ptr_shape.part_idx = SYNTHVID_CURSOR_COMPLETE;
|
| 332 | msg.ptr_shape.is_argb = 1;
|
| 333 | msg.ptr_shape.width = 1;
|
| 334 | msg.ptr_shape.height = 1;
|
| 335 | msg.ptr_shape.hot_x = 0;
|
| 336 | msg.ptr_shape.hot_y = 0;
|
| 337 | msg.ptr_shape.data[0] = 0;
|
| 338 | msg.ptr_shape.data[1] = 1;
|
| 339 | msg.ptr_shape.data[2] = 1;
|
| 340 | msg.ptr_shape.data[3] = 1;
|
| 341 | hv_drm_sendpacket(hdev, &msg);
|
| 342 |
|
| 343 | return 0;
|
| 344 | }
|
| 345 |
|
| 346 | int hv_drm_update_dirt(struct hv_device *hdev, struct drm_rect *rect)
|
| 347 | {
|
| 348 | struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
| 349 | struct synthvid_msg msg;
|
| 350 |
|
| 351 | if (!hv->dirt_needed)
|
| 352 | return 0;
|
| 353 |
|
| 354 | memset(&msg, 0, sizeof(struct synthvid_msg));
|
| 355 |
|
| 356 | msg.vid_hdr.type = SYNTHVID_DIRT;
|
| 357 | msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
|
| 358 | sizeof(struct synthvid_dirt);
|
| 359 | msg.dirt.video_output = 0;
|
| 360 | msg.dirt.dirt_count = 1;
|
| 361 | msg.dirt.rect[0].x1 = rect->x1;
|
| 362 | msg.dirt.rect[0].y1 = rect->y1;
|
| 363 | msg.dirt.rect[0].x2 = rect->x2;
|
| 364 | msg.dirt.rect[0].y2 = rect->y2;
|
| 365 |
|
| 366 | hv_drm_sendpacket(hdev, &msg);
|
| 367 |
|
| 368 | return 0;
|
| 369 | }
|
| 370 |
|
| 371 | static int hv_drm_get_supported_resolution(struct hv_device *hdev)
|
| 372 | {
|
| 373 | struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
| 374 | struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf;
|
| 375 | struct drm_device *dev = &hv->dev;
|
| 376 | unsigned long t;
|
| 377 | u8 index;
|
| 378 | int i;
|
| 379 |
|
| 380 | memset(msg, 0, sizeof(struct synthvid_msg));
|
| 381 | msg->vid_hdr.type = SYNTHVID_RESOLUTION_REQUEST;
|
| 382 | msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
|
| 383 | sizeof(struct synthvid_supported_resolution_req);
|
| 384 | msg->resolution_req.maximum_resolution_count =
|
| 385 | SYNTHVID_MAX_RESOLUTION_COUNT;
|
| 386 | hv_drm_sendpacket(hdev, msg);
|
| 387 |
|
| 388 | t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT);
|
| 389 | if (!t) {
|
| 390 | drm_err(dev, "Time out on waiting resolution response\n");
|
| 391 | return -ETIMEDOUT;
|
| 392 | }
|
| 393 |
|
| 394 | if (msg->resolution_resp.resolution_count == 0 ||
|
| 395 | msg->resolution_resp.resolution_count >
|
| 396 | SYNTHVID_MAX_RESOLUTION_COUNT) {
|
| 397 | drm_err(dev, "Invalid resolution count: %d\n",
|
| 398 | msg->resolution_resp.resolution_count);
|
| 399 | return -ENODEV;
|
| 400 | }
|
| 401 |
|
| 402 | index = msg->resolution_resp.default_resolution_index;
|
| 403 | if (index >= msg->resolution_resp.resolution_count) {
|
| 404 | drm_err(dev, "Invalid resolution index: %d\n", index);
|
| 405 | return -ENODEV;
|
| 406 | }
|
| 407 |
|
| 408 | for (i = 0; i < msg->resolution_resp.resolution_count; i++) {
|
| 409 | hv->screen_width_max = max_t(u32, hv->screen_width_max,
|
| 410 | msg->resolution_resp.supported_resolution[i].width);
|
| 411 | hv->screen_height_max = max_t(u32, hv->screen_height_max,
|
| 412 | msg->resolution_resp.supported_resolution[i].height);
|
| 413 | }
|
| 414 |
|
| 415 | hv->preferred_width =
|
| 416 | msg->resolution_resp.supported_resolution[index].width;
|
| 417 | hv->preferred_height =
|
| 418 | msg->resolution_resp.supported_resolution[index].height;
|
| 419 |
|
| 420 | return 0;
|
| 421 | }
|
| 422 |
|
| 423 | static void hv_drm_receive_sub(struct hv_device *hdev, u32 bytes_recvd)
|
| 424 | {
|
| 425 | struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
| 426 | struct synthvid_msg *msg;
|
| 427 | size_t hdr_size;
|
| 428 | size_t need;
|
| 429 |
|
| 430 | if (!hv)
|
| 431 | return;
|
| 432 |
|
| 433 | hdr_size = sizeof(struct pipe_msg_hdr) +
|
| 434 | sizeof(struct synthvid_msg_hdr);
|
| 435 | if (bytes_recvd < hdr_size) {
|
| 436 | drm_err_ratelimited(&hv->dev,
|
| 437 | "synthvid packet too small for header: %u\n",
|
| 438 | bytes_recvd);
|
| 439 | return;
|
| 440 | }
|
| 441 |
|
| 442 | msg = (struct synthvid_msg *)hv->recv_buf;
|
| 443 | need = hdr_size;
|
| 444 |
|
| 445 | switch (msg->vid_hdr.type) {
|
| 446 | case SYNTHVID_VERSION_RESPONSE:
|
| 447 | need += sizeof(struct synthvid_version_resp);
|
| 448 | break;
|
| 449 | case SYNTHVID_RESOLUTION_RESPONSE:
|
| 450 | |
| 451 | * The resolution response is variable length: the host
|
| 452 | * fills resolution_count entries, not the full
|
| 453 | * SYNTHVID_MAX_RESOLUTION_COUNT array. Require the fixed
|
| 454 | * prefix first so resolution_count can be read, then
|
| 455 | * demand exactly the count-sized array.
|
| 456 |
|
| 457 | need += offsetof(struct synthvid_supported_resolution_resp,
|
| 458 | supported_resolution);
|
| 459 | if (bytes_recvd < need)
|
| 460 | break;
|
| 461 | if (msg->resolution_resp.resolution_count >
|
| 462 | SYNTHVID_MAX_RESOLUTION_COUNT) {
|
| 463 | drm_err_ratelimited(&hv->dev,
|
| 464 | "synthvid resolution count too large: %u\n",
|
| 465 | msg->resolution_resp.resolution_count);
|
| 466 | return;
|
| 467 | }
|
| 468 | need += msg->resolution_resp.resolution_count *
|
| 469 | sizeof(struct hvd_screen_info);
|
| 470 | break;
|
| 471 | case SYNTHVID_VRAM_LOCATION_ACK:
|
| 472 | need += sizeof(struct synthvid_vram_location_ack);
|
| 473 | break;
|
| 474 | case SYNTHVID_FEATURE_CHANGE:
|
| 475 | |
| 476 | * Not a completion-driving message: validate its own payload
|
| 477 | * and consume it here rather than falling through to the
|
| 478 | * memcpy/complete shared by the wait-event responses.
|
| 479 |
|
| 480 | if (bytes_recvd < need +
|
| 481 | sizeof(struct synthvid_feature_change)) {
|
| 482 | drm_err_ratelimited(&hv->dev,
|
| 483 | "synthvid feature change packet too small: %u\n",
|
| 484 | bytes_recvd);
|
| 485 | return;
|
| 486 | }
|
| 487 | hv->dirt_needed = msg->feature_chg.is_dirt_needed;
|
| 488 | if (hv->dirt_needed)
|
| 489 | hv_drm_hide_hw_ptr(hv->hdev);
|
| 490 | return;
|
| 491 | default:
|
| 492 | return;
|
| 493 | }
|
| 494 |
|
| 495 | |
| 496 | * Shared completion path for the wait-event responses
|
| 497 | * (VERSION_RESPONSE, RESOLUTION_RESPONSE, VRAM_LOCATION_ACK):
|
| 498 | * require the type-specific payload before handing the buffer to
|
| 499 | * the waiter.
|
| 500 |
|
| 501 | if (bytes_recvd < need) {
|
| 502 | drm_err_ratelimited(&hv->dev,
|
| 503 | "synthvid packet too small for type %u: %u < %zu\n",
|
| 504 | msg->vid_hdr.type, bytes_recvd, need);
|
| 505 | return;
|
| 506 | }
|
| 507 | memcpy(hv->init_buf, msg, bytes_recvd);
|
| 508 | complete(&hv->wait);
|
| 509 | }
|
| 510 |
|
| 511 | static void hv_drm_receive(void *ctx)
|
| 512 | {
|
| 513 | struct hv_device *hdev = ctx;
|
| 514 | struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
| 515 | struct synthvid_msg *recv_buf;
|
| 516 | u32 bytes_recvd;
|
| 517 | u64 req_id;
|
| 518 | int ret;
|
| 519 |
|
| 520 | if (!hv)
|
| 521 | return;
|
| 522 |
|
| 523 | recv_buf = (struct synthvid_msg *)hv->recv_buf;
|
| 524 |
|
| 525 | do {
|
| 526 | ret = vmbus_recvpacket(hdev->channel, recv_buf,
|
| 527 | VMBUS_MAX_PACKET_SIZE,
|
| 528 | &bytes_recvd, &req_id);
|
| 529 | if (ret) {
|
| 530 | |
| 531 | * A nonzero return (e.g. -ENOBUFS for an oversized
|
| 532 | * packet) is itself a malformed message: bytes_recvd
|
| 533 | * then reports the required length rather than a copied
|
| 534 | * payload, so it must not be forwarded to the
|
| 535 | * sub-handler. Channel recovery is not attempted.
|
| 536 |
|
| 537 | drm_err_ratelimited(&hv->dev,
|
| 538 | "vmbus_recvpacket failed: %d (need %u)\n",
|
| 539 | ret, bytes_recvd);
|
| 540 | } else if (bytes_recvd > 0 &&
|
| 541 | recv_buf->pipe_hdr.type == PIPE_MSG_DATA) {
|
| 542 | hv_drm_receive_sub(hdev, bytes_recvd);
|
| 543 | }
|
| 544 | } while (bytes_recvd > 0 && ret == 0);
|
| 545 | }
|
| 546 |
|
| 547 | int hv_drm_connect_vsp(struct hv_device *hdev)
|
| 548 | {
|
| 549 | struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
| 550 | struct drm_device *dev = &hv->dev;
|
| 551 | int ret;
|
| 552 |
|
| 553 | ret = vmbus_open(hdev->channel, VMBUS_RING_BUFSIZE, VMBUS_RING_BUFSIZE,
|
| 554 | NULL, 0, hv_drm_receive, hdev);
|
| 555 | if (ret) {
|
| 556 | drm_err(dev, "Unable to open vmbus channel\n");
|
| 557 | return ret;
|
| 558 | }
|
| 559 |
|
| 560 |
|
| 561 | switch (vmbus_proto_version) {
|
| 562 | case VERSION_WIN10:
|
| 563 | case VERSION_WIN10_V5:
|
| 564 | ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10);
|
| 565 | if (!ret)
|
| 566 | break;
|
| 567 | fallthrough;
|
| 568 | case VERSION_WIN8:
|
| 569 | case VERSION_WIN8_1:
|
| 570 | ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN8);
|
| 571 | break;
|
| 572 | default:
|
| 573 | ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10);
|
| 574 | break;
|
| 575 | }
|
| 576 |
|
| 577 | if (ret) {
|
| 578 | drm_err(dev, "Synthetic video device version not accepted %d\n", ret);
|
| 579 | goto error;
|
| 580 | }
|
| 581 |
|
| 582 | hv->screen_depth = SYNTHVID_DEPTH_WIN8;
|
| 583 |
|
| 584 | if (hv_drm_version_ge(hv->synthvid_version, SYNTHVID_VERSION_WIN10)) {
|
| 585 | ret = hv_drm_get_supported_resolution(hdev);
|
| 586 | if (ret)
|
| 587 | drm_err(dev, "Failed to get supported resolution from host, use default\n");
|
| 588 | }
|
| 589 |
|
| 590 | if (!hv->screen_width_max) {
|
| 591 | hv->screen_width_max = SYNTHVID_WIDTH_WIN8;
|
| 592 | hv->screen_height_max = SYNTHVID_HEIGHT_WIN8;
|
| 593 | hv->preferred_width = SYNTHVID_WIDTH_WIN8;
|
| 594 | hv->preferred_height = SYNTHVID_HEIGHT_WIN8;
|
| 595 | }
|
| 596 |
|
| 597 | hv->mmio_megabytes = hdev->channel->offermsg.offer.mmio_megabytes;
|
| 598 |
|
| 599 | return 0;
|
| 600 |
|
| 601 | error:
|
| 602 | vmbus_close(hdev->channel);
|
| 603 | return ret;
|
| 604 | }
|