.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Copyright (c) 2012, Microsoft Corporation. |
---|
3 | 4 | * |
---|
4 | 5 | * Author: |
---|
5 | 6 | * Haiyang Zhang <haiyangz@microsoft.com> |
---|
6 | | - * |
---|
7 | | - * This program is free software; you can redistribute it and/or modify it |
---|
8 | | - * under the terms of the GNU General Public License version 2 as published |
---|
9 | | - * by the Free Software Foundation. |
---|
10 | | - * |
---|
11 | | - * This program is distributed in the hope that it will be useful, but |
---|
12 | | - * WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
13 | | - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or |
---|
14 | | - * NON INFRINGEMENT. See the GNU General Public License for more |
---|
15 | | - * details. |
---|
16 | 7 | */ |
---|
17 | 8 | |
---|
18 | 9 | /* |
---|
.. | .. |
---|
32 | 23 | * |
---|
33 | 24 | * Portrait orientation is also supported: |
---|
34 | 25 | * For example: video=hyperv_fb:864x1152 |
---|
| 26 | + * |
---|
| 27 | + * When a Windows 10 RS5+ host is used, the virtual machine screen |
---|
| 28 | + * resolution is obtained from the host. The "video=hyperv_fb" option is |
---|
| 29 | + * not needed, but still can be used to overwrite what the host specifies. |
---|
| 30 | + * The VM resolution on the host could be set by executing the powershell |
---|
| 31 | + * "set-vmvideo" command. For example |
---|
| 32 | + * set-vmvideo -vmname name -horizontalresolution:1920 \ |
---|
| 33 | + * -verticalresolution:1200 -resolutiontype single |
---|
| 34 | + * |
---|
| 35 | + * Gen 1 VMs also support direct using VM's physical memory for framebuffer. |
---|
| 36 | + * It could improve the efficiency and performance for framebuffer and VM. |
---|
| 37 | + * This requires to allocate contiguous physical memory from Linux kernel's |
---|
| 38 | + * CMA memory allocator. To enable this, supply a kernel parameter to give |
---|
| 39 | + * enough memory space to CMA allocator for framebuffer. For example: |
---|
| 40 | + * cma=130m |
---|
| 41 | + * This gives 130MB memory to CMA allocator that can be allocated to |
---|
| 42 | + * framebuffer. For reference, 8K resolution (7680x4320) takes about |
---|
| 43 | + * 127MB memory. |
---|
35 | 44 | */ |
---|
36 | 45 | |
---|
37 | 46 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
---|
38 | 47 | |
---|
39 | 48 | #include <linux/module.h> |
---|
40 | 49 | #include <linux/kernel.h> |
---|
| 50 | +#include <linux/vmalloc.h> |
---|
41 | 51 | #include <linux/init.h> |
---|
42 | 52 | #include <linux/completion.h> |
---|
43 | 53 | #include <linux/fb.h> |
---|
44 | 54 | #include <linux/pci.h> |
---|
45 | 55 | #include <linux/efi.h> |
---|
| 56 | +#include <linux/console.h> |
---|
46 | 57 | |
---|
47 | 58 | #include <linux/hyperv.h> |
---|
48 | 59 | |
---|
.. | .. |
---|
53 | 64 | #define SYNTHVID_VERSION(major, minor) ((minor) << 16 | (major)) |
---|
54 | 65 | #define SYNTHVID_VERSION_WIN7 SYNTHVID_VERSION(3, 0) |
---|
55 | 66 | #define SYNTHVID_VERSION_WIN8 SYNTHVID_VERSION(3, 2) |
---|
| 67 | +#define SYNTHVID_VERSION_WIN10 SYNTHVID_VERSION(3, 5) |
---|
| 68 | + |
---|
| 69 | +#define SYNTHVID_VER_GET_MAJOR(ver) (ver & 0x0000ffff) |
---|
| 70 | +#define SYNTHVID_VER_GET_MINOR(ver) ((ver & 0xffff0000) >> 16) |
---|
56 | 71 | |
---|
57 | 72 | #define SYNTHVID_DEPTH_WIN7 16 |
---|
58 | 73 | #define SYNTHVID_DEPTH_WIN8 32 |
---|
.. | .. |
---|
91 | 106 | SYNTHVID_POINTER_SHAPE = 8, |
---|
92 | 107 | SYNTHVID_FEATURE_CHANGE = 9, |
---|
93 | 108 | SYNTHVID_DIRT = 10, |
---|
| 109 | + SYNTHVID_RESOLUTION_REQUEST = 13, |
---|
| 110 | + SYNTHVID_RESOLUTION_RESPONSE = 14, |
---|
94 | 111 | |
---|
95 | | - SYNTHVID_MAX = 11 |
---|
| 112 | + SYNTHVID_MAX = 15 |
---|
96 | 113 | }; |
---|
| 114 | + |
---|
| 115 | +#define SYNTHVID_EDID_BLOCK_SIZE 128 |
---|
| 116 | +#define SYNTHVID_MAX_RESOLUTION_COUNT 64 |
---|
| 117 | + |
---|
| 118 | +struct hvd_screen_info { |
---|
| 119 | + u16 width; |
---|
| 120 | + u16 height; |
---|
| 121 | +} __packed; |
---|
97 | 122 | |
---|
98 | 123 | struct synthvid_msg_hdr { |
---|
99 | 124 | u32 type; |
---|
100 | 125 | u32 size; /* size of this header + payload after this field*/ |
---|
101 | 126 | } __packed; |
---|
102 | | - |
---|
103 | 127 | |
---|
104 | 128 | struct synthvid_version_req { |
---|
105 | 129 | u32 version; |
---|
.. | .. |
---|
109 | 133 | u32 version; |
---|
110 | 134 | u8 is_accepted; |
---|
111 | 135 | u8 max_video_outputs; |
---|
| 136 | +} __packed; |
---|
| 137 | + |
---|
| 138 | +struct synthvid_supported_resolution_req { |
---|
| 139 | + u8 maximum_resolution_count; |
---|
| 140 | +} __packed; |
---|
| 141 | + |
---|
| 142 | +struct synthvid_supported_resolution_resp { |
---|
| 143 | + u8 edid_block[SYNTHVID_EDID_BLOCK_SIZE]; |
---|
| 144 | + u8 resolution_count; |
---|
| 145 | + u8 default_resolution_index; |
---|
| 146 | + u8 is_standard; |
---|
| 147 | + struct hvd_screen_info |
---|
| 148 | + supported_resolution[SYNTHVID_MAX_RESOLUTION_COUNT]; |
---|
112 | 149 | } __packed; |
---|
113 | 150 | |
---|
114 | 151 | struct synthvid_vram_location { |
---|
.. | .. |
---|
196 | 233 | struct synthvid_pointer_shape ptr_shape; |
---|
197 | 234 | struct synthvid_feature_change feature_chg; |
---|
198 | 235 | struct synthvid_dirt dirt; |
---|
| 236 | + struct synthvid_supported_resolution_req resolution_req; |
---|
| 237 | + struct synthvid_supported_resolution_resp resolution_resp; |
---|
199 | 238 | }; |
---|
200 | 239 | } __packed; |
---|
201 | | - |
---|
202 | 240 | |
---|
203 | 241 | |
---|
204 | 242 | /* FB driver definitions and structures */ |
---|
.. | .. |
---|
210 | 248 | #define RING_BUFSIZE (256 * 1024) |
---|
211 | 249 | #define VSP_TIMEOUT (10 * HZ) |
---|
212 | 250 | #define HVFB_UPDATE_DELAY (HZ / 20) |
---|
| 251 | +#define HVFB_ONDEMAND_THROTTLE (HZ / 20) |
---|
213 | 252 | |
---|
214 | 253 | struct hvfb_par { |
---|
215 | 254 | struct fb_info *info; |
---|
.. | .. |
---|
220 | 259 | |
---|
221 | 260 | struct delayed_work dwork; |
---|
222 | 261 | bool update; |
---|
| 262 | + bool update_saved; /* The value of 'update' before hibernation */ |
---|
223 | 263 | |
---|
224 | 264 | u32 pseudo_palette[16]; |
---|
225 | 265 | u8 init_buf[MAX_VMBUS_PKT_SIZE]; |
---|
.. | .. |
---|
228 | 268 | /* If true, the VSC notifies the VSP on every framebuffer change */ |
---|
229 | 269 | bool synchronous_fb; |
---|
230 | 270 | |
---|
| 271 | + /* If true, need to copy from deferred IO mem to framebuffer mem */ |
---|
| 272 | + bool need_docopy; |
---|
| 273 | + |
---|
231 | 274 | struct notifier_block hvfb_panic_nb; |
---|
| 275 | + |
---|
| 276 | + /* Memory for deferred IO and frame buffer itself */ |
---|
| 277 | + unsigned char *dio_vp; |
---|
| 278 | + unsigned char *mmio_vp; |
---|
| 279 | + phys_addr_t mmio_pp; |
---|
| 280 | + |
---|
| 281 | + /* Dirty rectangle, protected by delayed_refresh_lock */ |
---|
| 282 | + int x1, y1, x2, y2; |
---|
| 283 | + bool delayed_refresh; |
---|
| 284 | + spinlock_t delayed_refresh_lock; |
---|
232 | 285 | }; |
---|
233 | 286 | |
---|
234 | 287 | static uint screen_width = HVFB_WIDTH; |
---|
235 | 288 | static uint screen_height = HVFB_HEIGHT; |
---|
236 | 289 | static uint screen_depth; |
---|
237 | 290 | static uint screen_fb_size; |
---|
| 291 | +static uint dio_fb_size; /* FB size for deferred IO */ |
---|
238 | 292 | |
---|
239 | 293 | /* Send message to Hyper-V host */ |
---|
240 | 294 | static inline int synthvid_send(struct hv_device *hdev, |
---|
.. | .. |
---|
321 | 375 | } |
---|
322 | 376 | |
---|
323 | 377 | /* Send updated screen area (dirty rectangle) location to host */ |
---|
324 | | -static int synthvid_update(struct fb_info *info) |
---|
| 378 | +static int |
---|
| 379 | +synthvid_update(struct fb_info *info, int x1, int y1, int x2, int y2) |
---|
325 | 380 | { |
---|
326 | 381 | struct hv_device *hdev = device_to_hv_device(info->device); |
---|
327 | 382 | struct synthvid_msg msg; |
---|
328 | 383 | |
---|
329 | 384 | memset(&msg, 0, sizeof(struct synthvid_msg)); |
---|
| 385 | + if (x2 == INT_MAX) |
---|
| 386 | + x2 = info->var.xres; |
---|
| 387 | + if (y2 == INT_MAX) |
---|
| 388 | + y2 = info->var.yres; |
---|
330 | 389 | |
---|
331 | 390 | msg.vid_hdr.type = SYNTHVID_DIRT; |
---|
332 | 391 | msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) + |
---|
333 | 392 | sizeof(struct synthvid_dirt); |
---|
334 | 393 | msg.dirt.video_output = 0; |
---|
335 | 394 | msg.dirt.dirt_count = 1; |
---|
336 | | - msg.dirt.rect[0].x1 = 0; |
---|
337 | | - msg.dirt.rect[0].y1 = 0; |
---|
338 | | - msg.dirt.rect[0].x2 = info->var.xres; |
---|
339 | | - msg.dirt.rect[0].y2 = info->var.yres; |
---|
| 395 | + msg.dirt.rect[0].x1 = (x1 > x2) ? 0 : x1; |
---|
| 396 | + msg.dirt.rect[0].y1 = (y1 > y2) ? 0 : y1; |
---|
| 397 | + msg.dirt.rect[0].x2 = |
---|
| 398 | + (x2 < x1 || x2 > info->var.xres) ? info->var.xres : x2; |
---|
| 399 | + msg.dirt.rect[0].y2 = |
---|
| 400 | + (y2 < y1 || y2 > info->var.yres) ? info->var.yres : y2; |
---|
340 | 401 | |
---|
341 | 402 | synthvid_send(hdev, &msg); |
---|
342 | 403 | |
---|
343 | 404 | return 0; |
---|
344 | 405 | } |
---|
345 | 406 | |
---|
| 407 | +static void hvfb_docopy(struct hvfb_par *par, |
---|
| 408 | + unsigned long offset, |
---|
| 409 | + unsigned long size) |
---|
| 410 | +{ |
---|
| 411 | + if (!par || !par->mmio_vp || !par->dio_vp || !par->fb_ready || |
---|
| 412 | + size == 0 || offset >= dio_fb_size) |
---|
| 413 | + return; |
---|
| 414 | + |
---|
| 415 | + if (offset + size > dio_fb_size) |
---|
| 416 | + size = dio_fb_size - offset; |
---|
| 417 | + |
---|
| 418 | + memcpy(par->mmio_vp + offset, par->dio_vp + offset, size); |
---|
| 419 | +} |
---|
| 420 | + |
---|
| 421 | +/* Deferred IO callback */ |
---|
| 422 | +static void synthvid_deferred_io(struct fb_info *p, |
---|
| 423 | + struct list_head *pagelist) |
---|
| 424 | +{ |
---|
| 425 | + struct hvfb_par *par = p->par; |
---|
| 426 | + struct page *page; |
---|
| 427 | + unsigned long start, end; |
---|
| 428 | + int y1, y2, miny, maxy; |
---|
| 429 | + |
---|
| 430 | + miny = INT_MAX; |
---|
| 431 | + maxy = 0; |
---|
| 432 | + |
---|
| 433 | + /* |
---|
| 434 | + * Merge dirty pages. It is possible that last page cross |
---|
| 435 | + * over the end of frame buffer row yres. This is taken care of |
---|
| 436 | + * in synthvid_update function by clamping the y2 |
---|
| 437 | + * value to yres. |
---|
| 438 | + */ |
---|
| 439 | + list_for_each_entry(page, pagelist, lru) { |
---|
| 440 | + start = page->index << PAGE_SHIFT; |
---|
| 441 | + end = start + PAGE_SIZE - 1; |
---|
| 442 | + y1 = start / p->fix.line_length; |
---|
| 443 | + y2 = end / p->fix.line_length; |
---|
| 444 | + miny = min_t(int, miny, y1); |
---|
| 445 | + maxy = max_t(int, maxy, y2); |
---|
| 446 | + |
---|
| 447 | + /* Copy from dio space to mmio address */ |
---|
| 448 | + if (par->fb_ready && par->need_docopy) |
---|
| 449 | + hvfb_docopy(par, start, PAGE_SIZE); |
---|
| 450 | + } |
---|
| 451 | + |
---|
| 452 | + if (par->fb_ready && par->update) |
---|
| 453 | + synthvid_update(p, 0, miny, p->var.xres, maxy + 1); |
---|
| 454 | +} |
---|
| 455 | + |
---|
| 456 | +static struct fb_deferred_io synthvid_defio = { |
---|
| 457 | + .delay = HZ / 20, |
---|
| 458 | + .deferred_io = synthvid_deferred_io, |
---|
| 459 | +}; |
---|
346 | 460 | |
---|
347 | 461 | /* |
---|
348 | 462 | * Actions on received messages from host: |
---|
.. | .. |
---|
363 | 477 | |
---|
364 | 478 | /* Complete the wait event */ |
---|
365 | 479 | if (msg->vid_hdr.type == SYNTHVID_VERSION_RESPONSE || |
---|
| 480 | + msg->vid_hdr.type == SYNTHVID_RESOLUTION_RESPONSE || |
---|
366 | 481 | msg->vid_hdr.type == SYNTHVID_VRAM_LOCATION_ACK) { |
---|
367 | 482 | memcpy(par->init_buf, msg, MAX_VMBUS_PKT_SIZE); |
---|
368 | 483 | complete(&par->wait); |
---|
.. | .. |
---|
409 | 524 | } while (bytes_recvd > 0 && ret == 0); |
---|
410 | 525 | } |
---|
411 | 526 | |
---|
| 527 | +/* Check if the ver1 version is equal or greater than ver2 */ |
---|
| 528 | +static inline bool synthvid_ver_ge(u32 ver1, u32 ver2) |
---|
| 529 | +{ |
---|
| 530 | + if (SYNTHVID_VER_GET_MAJOR(ver1) > SYNTHVID_VER_GET_MAJOR(ver2) || |
---|
| 531 | + (SYNTHVID_VER_GET_MAJOR(ver1) == SYNTHVID_VER_GET_MAJOR(ver2) && |
---|
| 532 | + SYNTHVID_VER_GET_MINOR(ver1) >= SYNTHVID_VER_GET_MINOR(ver2))) |
---|
| 533 | + return true; |
---|
| 534 | + |
---|
| 535 | + return false; |
---|
| 536 | +} |
---|
| 537 | + |
---|
412 | 538 | /* Check synthetic video protocol version with the host */ |
---|
413 | 539 | static int synthvid_negotiate_ver(struct hv_device *hdev, u32 ver) |
---|
414 | 540 | { |
---|
.. | .. |
---|
437 | 563 | } |
---|
438 | 564 | |
---|
439 | 565 | par->synthvid_version = ver; |
---|
| 566 | + pr_info("Synthvid Version major %d, minor %d\n", |
---|
| 567 | + SYNTHVID_VER_GET_MAJOR(ver), SYNTHVID_VER_GET_MINOR(ver)); |
---|
| 568 | + |
---|
| 569 | +out: |
---|
| 570 | + return ret; |
---|
| 571 | +} |
---|
| 572 | + |
---|
| 573 | +/* Get current resolution from the host */ |
---|
| 574 | +static int synthvid_get_supported_resolution(struct hv_device *hdev) |
---|
| 575 | +{ |
---|
| 576 | + struct fb_info *info = hv_get_drvdata(hdev); |
---|
| 577 | + struct hvfb_par *par = info->par; |
---|
| 578 | + struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf; |
---|
| 579 | + int ret = 0; |
---|
| 580 | + unsigned long t; |
---|
| 581 | + u8 index; |
---|
| 582 | + |
---|
| 583 | + memset(msg, 0, sizeof(struct synthvid_msg)); |
---|
| 584 | + msg->vid_hdr.type = SYNTHVID_RESOLUTION_REQUEST; |
---|
| 585 | + msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) + |
---|
| 586 | + sizeof(struct synthvid_supported_resolution_req); |
---|
| 587 | + |
---|
| 588 | + msg->resolution_req.maximum_resolution_count = |
---|
| 589 | + SYNTHVID_MAX_RESOLUTION_COUNT; |
---|
| 590 | + synthvid_send(hdev, msg); |
---|
| 591 | + |
---|
| 592 | + t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT); |
---|
| 593 | + if (!t) { |
---|
| 594 | + pr_err("Time out on waiting resolution response\n"); |
---|
| 595 | + ret = -ETIMEDOUT; |
---|
| 596 | + goto out; |
---|
| 597 | + } |
---|
| 598 | + |
---|
| 599 | + if (msg->resolution_resp.resolution_count == 0) { |
---|
| 600 | + pr_err("No supported resolutions\n"); |
---|
| 601 | + ret = -ENODEV; |
---|
| 602 | + goto out; |
---|
| 603 | + } |
---|
| 604 | + |
---|
| 605 | + index = msg->resolution_resp.default_resolution_index; |
---|
| 606 | + if (index >= msg->resolution_resp.resolution_count) { |
---|
| 607 | + pr_err("Invalid resolution index: %d\n", index); |
---|
| 608 | + ret = -ENODEV; |
---|
| 609 | + goto out; |
---|
| 610 | + } |
---|
| 611 | + |
---|
| 612 | + screen_width = |
---|
| 613 | + msg->resolution_resp.supported_resolution[index].width; |
---|
| 614 | + screen_height = |
---|
| 615 | + msg->resolution_resp.supported_resolution[index].height; |
---|
440 | 616 | |
---|
441 | 617 | out: |
---|
442 | 618 | return ret; |
---|
.. | .. |
---|
457 | 633 | } |
---|
458 | 634 | |
---|
459 | 635 | /* Negotiate the protocol version with host */ |
---|
460 | | - if (vmbus_proto_version == VERSION_WS2008 || |
---|
461 | | - vmbus_proto_version == VERSION_WIN7) |
---|
462 | | - ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN7); |
---|
463 | | - else |
---|
| 636 | + switch (vmbus_proto_version) { |
---|
| 637 | + case VERSION_WIN10: |
---|
| 638 | + case VERSION_WIN10_V5: |
---|
| 639 | + ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN10); |
---|
| 640 | + if (!ret) |
---|
| 641 | + break; |
---|
| 642 | + fallthrough; |
---|
| 643 | + case VERSION_WIN8: |
---|
| 644 | + case VERSION_WIN8_1: |
---|
464 | 645 | ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN8); |
---|
| 646 | + if (!ret) |
---|
| 647 | + break; |
---|
| 648 | + fallthrough; |
---|
| 649 | + case VERSION_WS2008: |
---|
| 650 | + case VERSION_WIN7: |
---|
| 651 | + ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN7); |
---|
| 652 | + break; |
---|
| 653 | + default: |
---|
| 654 | + ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN10); |
---|
| 655 | + break; |
---|
| 656 | + } |
---|
465 | 657 | |
---|
466 | 658 | if (ret) { |
---|
467 | 659 | pr_err("Synthetic video device version not accepted\n"); |
---|
.. | .. |
---|
472 | 664 | screen_depth = SYNTHVID_DEPTH_WIN7; |
---|
473 | 665 | else |
---|
474 | 666 | screen_depth = SYNTHVID_DEPTH_WIN8; |
---|
| 667 | + |
---|
| 668 | + if (synthvid_ver_ge(par->synthvid_version, SYNTHVID_VERSION_WIN10)) { |
---|
| 669 | + ret = synthvid_get_supported_resolution(hdev); |
---|
| 670 | + if (ret) |
---|
| 671 | + pr_info("Failed to get supported resolution from host, use default\n"); |
---|
| 672 | + } |
---|
475 | 673 | |
---|
476 | 674 | screen_fb_size = hdev->channel->offermsg.offer. |
---|
477 | 675 | mmio_megabytes * 1024 * 1024; |
---|
.. | .. |
---|
497 | 695 | msg->vid_hdr.type = SYNTHVID_VRAM_LOCATION; |
---|
498 | 696 | msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) + |
---|
499 | 697 | sizeof(struct synthvid_vram_location); |
---|
500 | | - msg->vram.user_ctx = msg->vram.vram_gpa = info->fix.smem_start; |
---|
| 698 | + msg->vram.user_ctx = msg->vram.vram_gpa = par->mmio_pp; |
---|
501 | 699 | msg->vram.is_vram_gpa_specified = 1; |
---|
502 | 700 | synthvid_send(hdev, msg); |
---|
503 | 701 | |
---|
.. | .. |
---|
507 | 705 | ret = -ETIMEDOUT; |
---|
508 | 706 | goto out; |
---|
509 | 707 | } |
---|
510 | | - if (msg->vram_ack.user_ctx != info->fix.smem_start) { |
---|
| 708 | + if (msg->vram_ack.user_ctx != par->mmio_pp) { |
---|
511 | 709 | pr_err("Unable to set VRAM location\n"); |
---|
512 | 710 | ret = -ENODEV; |
---|
513 | 711 | goto out; |
---|
.. | .. |
---|
524 | 722 | |
---|
525 | 723 | /* |
---|
526 | 724 | * Delayed work callback: |
---|
527 | | - * It is called at HVFB_UPDATE_DELAY or longer time interval to process |
---|
528 | | - * screen updates. It is re-scheduled if further update is necessary. |
---|
| 725 | + * It is scheduled to call whenever update request is received and it has |
---|
| 726 | + * not been called in last HVFB_ONDEMAND_THROTTLE time interval. |
---|
529 | 727 | */ |
---|
530 | 728 | static void hvfb_update_work(struct work_struct *w) |
---|
531 | 729 | { |
---|
532 | 730 | struct hvfb_par *par = container_of(w, struct hvfb_par, dwork.work); |
---|
533 | 731 | struct fb_info *info = par->info; |
---|
| 732 | + unsigned long flags; |
---|
| 733 | + int x1, x2, y1, y2; |
---|
| 734 | + int j; |
---|
534 | 735 | |
---|
535 | | - if (par->fb_ready) |
---|
536 | | - synthvid_update(info); |
---|
| 736 | + spin_lock_irqsave(&par->delayed_refresh_lock, flags); |
---|
| 737 | + /* Reset the request flag */ |
---|
| 738 | + par->delayed_refresh = false; |
---|
537 | 739 | |
---|
538 | | - if (par->update) |
---|
539 | | - schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY); |
---|
| 740 | + /* Store the dirty rectangle to local variables */ |
---|
| 741 | + x1 = par->x1; |
---|
| 742 | + x2 = par->x2; |
---|
| 743 | + y1 = par->y1; |
---|
| 744 | + y2 = par->y2; |
---|
| 745 | + |
---|
| 746 | + /* Clear dirty rectangle */ |
---|
| 747 | + par->x1 = par->y1 = INT_MAX; |
---|
| 748 | + par->x2 = par->y2 = 0; |
---|
| 749 | + |
---|
| 750 | + spin_unlock_irqrestore(&par->delayed_refresh_lock, flags); |
---|
| 751 | + |
---|
| 752 | + if (x1 > info->var.xres || x2 > info->var.xres || |
---|
| 753 | + y1 > info->var.yres || y2 > info->var.yres || x2 <= x1) |
---|
| 754 | + return; |
---|
| 755 | + |
---|
| 756 | + /* Copy the dirty rectangle to frame buffer memory */ |
---|
| 757 | + if (par->need_docopy) |
---|
| 758 | + for (j = y1; j < y2; j++) |
---|
| 759 | + hvfb_docopy(par, |
---|
| 760 | + j * info->fix.line_length + |
---|
| 761 | + (x1 * screen_depth / 8), |
---|
| 762 | + (x2 - x1) * screen_depth / 8); |
---|
| 763 | + |
---|
| 764 | + /* Refresh */ |
---|
| 765 | + if (par->fb_ready && par->update) |
---|
| 766 | + synthvid_update(info, x1, y1, x2, y2); |
---|
| 767 | +} |
---|
| 768 | + |
---|
| 769 | +/* |
---|
| 770 | + * Control the on-demand refresh frequency. It schedules a delayed |
---|
| 771 | + * screen update if it has not yet. |
---|
| 772 | + */ |
---|
| 773 | +static void hvfb_ondemand_refresh_throttle(struct hvfb_par *par, |
---|
| 774 | + int x1, int y1, int w, int h) |
---|
| 775 | +{ |
---|
| 776 | + unsigned long flags; |
---|
| 777 | + int x2 = x1 + w; |
---|
| 778 | + int y2 = y1 + h; |
---|
| 779 | + |
---|
| 780 | + spin_lock_irqsave(&par->delayed_refresh_lock, flags); |
---|
| 781 | + |
---|
| 782 | + /* Merge dirty rectangle */ |
---|
| 783 | + par->x1 = min_t(int, par->x1, x1); |
---|
| 784 | + par->y1 = min_t(int, par->y1, y1); |
---|
| 785 | + par->x2 = max_t(int, par->x2, x2); |
---|
| 786 | + par->y2 = max_t(int, par->y2, y2); |
---|
| 787 | + |
---|
| 788 | + /* Schedule a delayed screen update if not yet */ |
---|
| 789 | + if (par->delayed_refresh == false) { |
---|
| 790 | + schedule_delayed_work(&par->dwork, |
---|
| 791 | + HVFB_ONDEMAND_THROTTLE); |
---|
| 792 | + par->delayed_refresh = true; |
---|
| 793 | + } |
---|
| 794 | + |
---|
| 795 | + spin_unlock_irqrestore(&par->delayed_refresh_lock, flags); |
---|
540 | 796 | } |
---|
541 | 797 | |
---|
542 | 798 | static int hvfb_on_panic(struct notifier_block *nb, |
---|
.. | .. |
---|
548 | 804 | par = container_of(nb, struct hvfb_par, hvfb_panic_nb); |
---|
549 | 805 | par->synchronous_fb = true; |
---|
550 | 806 | info = par->info; |
---|
551 | | - synthvid_update(info); |
---|
| 807 | + if (par->need_docopy) |
---|
| 808 | + hvfb_docopy(par, 0, dio_fb_size); |
---|
| 809 | + synthvid_update(info, 0, 0, INT_MAX, INT_MAX); |
---|
552 | 810 | |
---|
553 | 811 | return NOTIFY_DONE; |
---|
554 | 812 | } |
---|
.. | .. |
---|
609 | 867 | |
---|
610 | 868 | cfb_fillrect(p, rect); |
---|
611 | 869 | if (par->synchronous_fb) |
---|
612 | | - synthvid_update(p); |
---|
| 870 | + synthvid_update(p, 0, 0, INT_MAX, INT_MAX); |
---|
| 871 | + else |
---|
| 872 | + hvfb_ondemand_refresh_throttle(par, rect->dx, rect->dy, |
---|
| 873 | + rect->width, rect->height); |
---|
613 | 874 | } |
---|
614 | 875 | |
---|
615 | 876 | static void hvfb_cfb_copyarea(struct fb_info *p, |
---|
.. | .. |
---|
619 | 880 | |
---|
620 | 881 | cfb_copyarea(p, area); |
---|
621 | 882 | if (par->synchronous_fb) |
---|
622 | | - synthvid_update(p); |
---|
| 883 | + synthvid_update(p, 0, 0, INT_MAX, INT_MAX); |
---|
| 884 | + else |
---|
| 885 | + hvfb_ondemand_refresh_throttle(par, area->dx, area->dy, |
---|
| 886 | + area->width, area->height); |
---|
623 | 887 | } |
---|
624 | 888 | |
---|
625 | 889 | static void hvfb_cfb_imageblit(struct fb_info *p, |
---|
.. | .. |
---|
629 | 893 | |
---|
630 | 894 | cfb_imageblit(p, image); |
---|
631 | 895 | if (par->synchronous_fb) |
---|
632 | | - synthvid_update(p); |
---|
| 896 | + synthvid_update(p, 0, 0, INT_MAX, INT_MAX); |
---|
| 897 | + else |
---|
| 898 | + hvfb_ondemand_refresh_throttle(par, image->dx, image->dy, |
---|
| 899 | + image->width, image->height); |
---|
633 | 900 | } |
---|
634 | 901 | |
---|
635 | | -static struct fb_ops hvfb_ops = { |
---|
| 902 | +static const struct fb_ops hvfb_ops = { |
---|
636 | 903 | .owner = THIS_MODULE, |
---|
637 | 904 | .fb_check_var = hvfb_check_var, |
---|
638 | 905 | .fb_set_par = hvfb_set_par, |
---|
.. | .. |
---|
662 | 929 | } |
---|
663 | 930 | |
---|
664 | 931 | if (x < HVFB_WIDTH_MIN || y < HVFB_HEIGHT_MIN || |
---|
| 932 | + (synthvid_ver_ge(par->synthvid_version, SYNTHVID_VERSION_WIN10) && |
---|
| 933 | + (x * y * screen_depth / 8 > screen_fb_size)) || |
---|
665 | 934 | (par->synthvid_version == SYNTHVID_VERSION_WIN8 && |
---|
666 | 935 | x * y * screen_depth / 8 > SYNTHVID_FB_SIZE_WIN8) || |
---|
667 | 936 | (par->synthvid_version == SYNTHVID_VERSION_WIN7 && |
---|
.. | .. |
---|
675 | 944 | return; |
---|
676 | 945 | } |
---|
677 | 946 | |
---|
| 947 | +/* |
---|
| 948 | + * Allocate enough contiguous physical memory. |
---|
| 949 | + * Return physical address if succeeded or -1 if failed. |
---|
| 950 | + */ |
---|
| 951 | +static phys_addr_t hvfb_get_phymem(struct hv_device *hdev, |
---|
| 952 | + unsigned int request_size) |
---|
| 953 | +{ |
---|
| 954 | + struct page *page = NULL; |
---|
| 955 | + dma_addr_t dma_handle; |
---|
| 956 | + void *vmem; |
---|
| 957 | + phys_addr_t paddr = 0; |
---|
| 958 | + unsigned int order = get_order(request_size); |
---|
| 959 | + |
---|
| 960 | + if (request_size == 0) |
---|
| 961 | + return -1; |
---|
| 962 | + |
---|
| 963 | + if (order < MAX_ORDER) { |
---|
| 964 | + /* Call alloc_pages if the size is less than 2^MAX_ORDER */ |
---|
| 965 | + page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); |
---|
| 966 | + if (!page) |
---|
| 967 | + return -1; |
---|
| 968 | + |
---|
| 969 | + paddr = (page_to_pfn(page) << PAGE_SHIFT); |
---|
| 970 | + } else { |
---|
| 971 | + /* Allocate from CMA */ |
---|
| 972 | + hdev->device.coherent_dma_mask = DMA_BIT_MASK(64); |
---|
| 973 | + |
---|
| 974 | + vmem = dma_alloc_coherent(&hdev->device, |
---|
| 975 | + round_up(request_size, PAGE_SIZE), |
---|
| 976 | + &dma_handle, |
---|
| 977 | + GFP_KERNEL | __GFP_NOWARN); |
---|
| 978 | + |
---|
| 979 | + if (!vmem) |
---|
| 980 | + return -1; |
---|
| 981 | + |
---|
| 982 | + paddr = virt_to_phys(vmem); |
---|
| 983 | + } |
---|
| 984 | + |
---|
| 985 | + return paddr; |
---|
| 986 | +} |
---|
| 987 | + |
---|
| 988 | +/* Release contiguous physical memory */ |
---|
| 989 | +static void hvfb_release_phymem(struct hv_device *hdev, |
---|
| 990 | + phys_addr_t paddr, unsigned int size) |
---|
| 991 | +{ |
---|
| 992 | + unsigned int order = get_order(size); |
---|
| 993 | + |
---|
| 994 | + if (order < MAX_ORDER) |
---|
| 995 | + __free_pages(pfn_to_page(paddr >> PAGE_SHIFT), order); |
---|
| 996 | + else |
---|
| 997 | + dma_free_coherent(&hdev->device, |
---|
| 998 | + round_up(size, PAGE_SIZE), |
---|
| 999 | + phys_to_virt(paddr), |
---|
| 1000 | + paddr); |
---|
| 1001 | +} |
---|
| 1002 | + |
---|
678 | 1003 | |
---|
679 | 1004 | /* Get framebuffer memory from Hyper-V video pci space */ |
---|
680 | 1005 | static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info) |
---|
.. | .. |
---|
683 | 1008 | struct pci_dev *pdev = NULL; |
---|
684 | 1009 | void __iomem *fb_virt; |
---|
685 | 1010 | int gen2vm = efi_enabled(EFI_BOOT); |
---|
686 | | - resource_size_t pot_start, pot_end; |
---|
| 1011 | + phys_addr_t paddr; |
---|
687 | 1012 | int ret; |
---|
688 | 1013 | |
---|
689 | | - if (gen2vm) { |
---|
690 | | - pot_start = 0; |
---|
691 | | - pot_end = -1; |
---|
692 | | - } else { |
---|
| 1014 | + info->apertures = alloc_apertures(1); |
---|
| 1015 | + if (!info->apertures) |
---|
| 1016 | + return -ENOMEM; |
---|
| 1017 | + |
---|
| 1018 | + if (!gen2vm) { |
---|
693 | 1019 | pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT, |
---|
694 | | - PCI_DEVICE_ID_HYPERV_VIDEO, NULL); |
---|
| 1020 | + PCI_DEVICE_ID_HYPERV_VIDEO, NULL); |
---|
695 | 1021 | if (!pdev) { |
---|
696 | 1022 | pr_err("Unable to find PCI Hyper-V video\n"); |
---|
697 | 1023 | return -ENODEV; |
---|
698 | 1024 | } |
---|
699 | 1025 | |
---|
700 | | - if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) || |
---|
701 | | - pci_resource_len(pdev, 0) < screen_fb_size) |
---|
702 | | - goto err1; |
---|
| 1026 | + info->apertures->ranges[0].base = pci_resource_start(pdev, 0); |
---|
| 1027 | + info->apertures->ranges[0].size = pci_resource_len(pdev, 0); |
---|
703 | 1028 | |
---|
704 | | - pot_end = pci_resource_end(pdev, 0); |
---|
705 | | - pot_start = pot_end - screen_fb_size + 1; |
---|
| 1029 | + /* |
---|
| 1030 | + * For Gen 1 VM, we can directly use the contiguous memory |
---|
| 1031 | + * from VM. If we succeed, deferred IO happens directly |
---|
| 1032 | + * on this allocated framebuffer memory, avoiding extra |
---|
| 1033 | + * memory copy. |
---|
| 1034 | + */ |
---|
| 1035 | + paddr = hvfb_get_phymem(hdev, screen_fb_size); |
---|
| 1036 | + if (paddr != (phys_addr_t) -1) { |
---|
| 1037 | + par->mmio_pp = paddr; |
---|
| 1038 | + par->mmio_vp = par->dio_vp = __va(paddr); |
---|
| 1039 | + |
---|
| 1040 | + info->fix.smem_start = paddr; |
---|
| 1041 | + info->fix.smem_len = screen_fb_size; |
---|
| 1042 | + info->screen_base = par->mmio_vp; |
---|
| 1043 | + info->screen_size = screen_fb_size; |
---|
| 1044 | + |
---|
| 1045 | + par->need_docopy = false; |
---|
| 1046 | + goto getmem_done; |
---|
| 1047 | + } |
---|
| 1048 | + pr_info("Unable to allocate enough contiguous physical memory on Gen 1 VM. Using MMIO instead.\n"); |
---|
| 1049 | + } else { |
---|
| 1050 | + info->apertures->ranges[0].base = screen_info.lfb_base; |
---|
| 1051 | + info->apertures->ranges[0].size = screen_info.lfb_size; |
---|
706 | 1052 | } |
---|
707 | 1053 | |
---|
708 | | - ret = vmbus_allocate_mmio(&par->mem, hdev, pot_start, pot_end, |
---|
| 1054 | + /* |
---|
| 1055 | + * Cannot use the contiguous physical memory. |
---|
| 1056 | + * Allocate mmio space for framebuffer. |
---|
| 1057 | + */ |
---|
| 1058 | + dio_fb_size = |
---|
| 1059 | + screen_width * screen_height * screen_depth / 8; |
---|
| 1060 | + |
---|
| 1061 | + ret = vmbus_allocate_mmio(&par->mem, hdev, 0, -1, |
---|
709 | 1062 | screen_fb_size, 0x100000, true); |
---|
710 | 1063 | if (ret != 0) { |
---|
711 | 1064 | pr_err("Unable to allocate framebuffer memory\n"); |
---|
.. | .. |
---|
713 | 1066 | } |
---|
714 | 1067 | |
---|
715 | 1068 | /* |
---|
716 | | - * Map the VRAM cacheable for performance. |
---|
| 1069 | + * Map the VRAM cacheable for performance. This is also required for |
---|
| 1070 | + * VM Connect to display properly for ARM64 Linux VM, as the host also |
---|
| 1071 | + * maps the VRAM cacheable. |
---|
717 | 1072 | */ |
---|
718 | | - fb_virt = ioremap_wc(par->mem->start, screen_fb_size); |
---|
| 1073 | + fb_virt = ioremap_cache(par->mem->start, screen_fb_size); |
---|
719 | 1074 | if (!fb_virt) |
---|
720 | 1075 | goto err2; |
---|
721 | 1076 | |
---|
722 | | - info->apertures = alloc_apertures(1); |
---|
723 | | - if (!info->apertures) |
---|
| 1077 | + /* Allocate memory for deferred IO */ |
---|
| 1078 | + par->dio_vp = vzalloc(round_up(dio_fb_size, PAGE_SIZE)); |
---|
| 1079 | + if (par->dio_vp == NULL) |
---|
724 | 1080 | goto err3; |
---|
725 | 1081 | |
---|
726 | | - if (gen2vm) { |
---|
727 | | - info->apertures->ranges[0].base = screen_info.lfb_base; |
---|
728 | | - info->apertures->ranges[0].size = screen_info.lfb_size; |
---|
729 | | - remove_conflicting_framebuffers(info->apertures, |
---|
730 | | - KBUILD_MODNAME, false); |
---|
731 | | - } else { |
---|
732 | | - info->apertures->ranges[0].base = pci_resource_start(pdev, 0); |
---|
733 | | - info->apertures->ranges[0].size = pci_resource_len(pdev, 0); |
---|
734 | | - } |
---|
| 1082 | + /* Physical address of FB device */ |
---|
| 1083 | + par->mmio_pp = par->mem->start; |
---|
| 1084 | + /* Virtual address of FB device */ |
---|
| 1085 | + par->mmio_vp = (unsigned char *) fb_virt; |
---|
735 | 1086 | |
---|
736 | 1087 | info->fix.smem_start = par->mem->start; |
---|
737 | | - info->fix.smem_len = screen_fb_size; |
---|
738 | | - info->screen_base = fb_virt; |
---|
739 | | - info->screen_size = screen_fb_size; |
---|
| 1088 | + info->fix.smem_len = dio_fb_size; |
---|
| 1089 | + info->screen_base = par->dio_vp; |
---|
| 1090 | + info->screen_size = dio_fb_size; |
---|
740 | 1091 | |
---|
741 | | - if (!gen2vm) |
---|
| 1092 | +getmem_done: |
---|
| 1093 | + remove_conflicting_framebuffers(info->apertures, |
---|
| 1094 | + KBUILD_MODNAME, false); |
---|
| 1095 | + |
---|
| 1096 | + if (gen2vm) { |
---|
| 1097 | + /* framebuffer is reallocated, clear screen_info to avoid misuse from kexec */ |
---|
| 1098 | + screen_info.lfb_size = 0; |
---|
| 1099 | + screen_info.lfb_base = 0; |
---|
| 1100 | + screen_info.orig_video_isVGA = 0; |
---|
| 1101 | + } else { |
---|
742 | 1102 | pci_dev_put(pdev); |
---|
| 1103 | + } |
---|
743 | 1104 | |
---|
744 | 1105 | return 0; |
---|
745 | 1106 | |
---|
.. | .. |
---|
756 | 1117 | } |
---|
757 | 1118 | |
---|
758 | 1119 | /* Release the framebuffer */ |
---|
759 | | -static void hvfb_putmem(struct fb_info *info) |
---|
| 1120 | +static void hvfb_putmem(struct hv_device *hdev, struct fb_info *info) |
---|
760 | 1121 | { |
---|
761 | 1122 | struct hvfb_par *par = info->par; |
---|
762 | 1123 | |
---|
763 | | - iounmap(info->screen_base); |
---|
764 | | - vmbus_free_mmio(par->mem->start, screen_fb_size); |
---|
| 1124 | + if (par->need_docopy) { |
---|
| 1125 | + vfree(par->dio_vp); |
---|
| 1126 | + iounmap(info->screen_base); |
---|
| 1127 | + vmbus_free_mmio(par->mem->start, screen_fb_size); |
---|
| 1128 | + } else { |
---|
| 1129 | + hvfb_release_phymem(hdev, info->fix.smem_start, |
---|
| 1130 | + screen_fb_size); |
---|
| 1131 | + } |
---|
| 1132 | + |
---|
765 | 1133 | par->mem = NULL; |
---|
766 | 1134 | } |
---|
767 | 1135 | |
---|
.. | .. |
---|
774 | 1142 | int ret; |
---|
775 | 1143 | |
---|
776 | 1144 | info = framebuffer_alloc(sizeof(struct hvfb_par), &hdev->device); |
---|
777 | | - if (!info) { |
---|
778 | | - pr_err("No memory for framebuffer info\n"); |
---|
| 1145 | + if (!info) |
---|
779 | 1146 | return -ENOMEM; |
---|
780 | | - } |
---|
781 | 1147 | |
---|
782 | 1148 | par = info->par; |
---|
783 | 1149 | par->info = info; |
---|
784 | 1150 | par->fb_ready = false; |
---|
| 1151 | + par->need_docopy = true; |
---|
785 | 1152 | init_completion(&par->wait); |
---|
786 | 1153 | INIT_DELAYED_WORK(&par->dwork, hvfb_update_work); |
---|
| 1154 | + |
---|
| 1155 | + par->delayed_refresh = false; |
---|
| 1156 | + spin_lock_init(&par->delayed_refresh_lock); |
---|
| 1157 | + par->x1 = par->y1 = INT_MAX; |
---|
| 1158 | + par->x2 = par->y2 = 0; |
---|
787 | 1159 | |
---|
788 | 1160 | /* Connect to VSP */ |
---|
789 | 1161 | hv_set_drvdata(hdev, info); |
---|
.. | .. |
---|
793 | 1165 | goto error1; |
---|
794 | 1166 | } |
---|
795 | 1167 | |
---|
| 1168 | + hvfb_get_option(info); |
---|
| 1169 | + pr_info("Screen resolution: %dx%d, Color depth: %d, Frame buffer size: %d\n", |
---|
| 1170 | + screen_width, screen_height, screen_depth, screen_fb_size); |
---|
| 1171 | + |
---|
796 | 1172 | ret = hvfb_getmem(hdev, info); |
---|
797 | 1173 | if (ret) { |
---|
798 | 1174 | pr_err("No memory for framebuffer\n"); |
---|
799 | 1175 | goto error2; |
---|
800 | 1176 | } |
---|
801 | | - |
---|
802 | | - hvfb_get_option(info); |
---|
803 | | - pr_info("Screen resolution: %dx%d, Color depth: %d\n", |
---|
804 | | - screen_width, screen_height, screen_depth); |
---|
805 | | - |
---|
806 | 1177 | |
---|
807 | 1178 | /* Set up fb_info */ |
---|
808 | 1179 | info->flags = FBINFO_DEFAULT; |
---|
.. | .. |
---|
837 | 1208 | info->fbops = &hvfb_ops; |
---|
838 | 1209 | info->pseudo_palette = par->pseudo_palette; |
---|
839 | 1210 | |
---|
| 1211 | + /* Initialize deferred IO */ |
---|
| 1212 | + info->fbdefio = &synthvid_defio; |
---|
| 1213 | + fb_deferred_io_init(info); |
---|
| 1214 | + |
---|
840 | 1215 | /* Send config to host */ |
---|
841 | 1216 | ret = synthvid_send_config(hdev); |
---|
842 | 1217 | if (ret) |
---|
.. | .. |
---|
858 | 1233 | return 0; |
---|
859 | 1234 | |
---|
860 | 1235 | error: |
---|
861 | | - hvfb_putmem(info); |
---|
| 1236 | + fb_deferred_io_cleanup(info); |
---|
| 1237 | + hvfb_putmem(hdev, info); |
---|
862 | 1238 | error2: |
---|
863 | 1239 | vmbus_close(hdev->channel); |
---|
864 | 1240 | error1: |
---|
.. | .. |
---|
880 | 1256 | par->update = false; |
---|
881 | 1257 | par->fb_ready = false; |
---|
882 | 1258 | |
---|
| 1259 | + fb_deferred_io_cleanup(info); |
---|
| 1260 | + |
---|
883 | 1261 | unregister_framebuffer(info); |
---|
884 | 1262 | cancel_delayed_work_sync(&par->dwork); |
---|
885 | 1263 | |
---|
886 | 1264 | vmbus_close(hdev->channel); |
---|
887 | 1265 | hv_set_drvdata(hdev, NULL); |
---|
888 | 1266 | |
---|
889 | | - hvfb_putmem(info); |
---|
| 1267 | + hvfb_putmem(hdev, info); |
---|
890 | 1268 | framebuffer_release(info); |
---|
891 | 1269 | |
---|
892 | 1270 | return 0; |
---|
| 1271 | +} |
---|
| 1272 | + |
---|
| 1273 | +static int hvfb_suspend(struct hv_device *hdev) |
---|
| 1274 | +{ |
---|
| 1275 | + struct fb_info *info = hv_get_drvdata(hdev); |
---|
| 1276 | + struct hvfb_par *par = info->par; |
---|
| 1277 | + |
---|
| 1278 | + console_lock(); |
---|
| 1279 | + |
---|
| 1280 | + /* 1 means do suspend */ |
---|
| 1281 | + fb_set_suspend(info, 1); |
---|
| 1282 | + |
---|
| 1283 | + cancel_delayed_work_sync(&par->dwork); |
---|
| 1284 | + cancel_delayed_work_sync(&info->deferred_work); |
---|
| 1285 | + |
---|
| 1286 | + par->update_saved = par->update; |
---|
| 1287 | + par->update = false; |
---|
| 1288 | + par->fb_ready = false; |
---|
| 1289 | + |
---|
| 1290 | + vmbus_close(hdev->channel); |
---|
| 1291 | + |
---|
| 1292 | + console_unlock(); |
---|
| 1293 | + |
---|
| 1294 | + return 0; |
---|
| 1295 | +} |
---|
| 1296 | + |
---|
| 1297 | +static int hvfb_resume(struct hv_device *hdev) |
---|
| 1298 | +{ |
---|
| 1299 | + struct fb_info *info = hv_get_drvdata(hdev); |
---|
| 1300 | + struct hvfb_par *par = info->par; |
---|
| 1301 | + int ret; |
---|
| 1302 | + |
---|
| 1303 | + console_lock(); |
---|
| 1304 | + |
---|
| 1305 | + ret = synthvid_connect_vsp(hdev); |
---|
| 1306 | + if (ret != 0) |
---|
| 1307 | + goto out; |
---|
| 1308 | + |
---|
| 1309 | + ret = synthvid_send_config(hdev); |
---|
| 1310 | + if (ret != 0) { |
---|
| 1311 | + vmbus_close(hdev->channel); |
---|
| 1312 | + goto out; |
---|
| 1313 | + } |
---|
| 1314 | + |
---|
| 1315 | + par->fb_ready = true; |
---|
| 1316 | + par->update = par->update_saved; |
---|
| 1317 | + |
---|
| 1318 | + schedule_delayed_work(&info->deferred_work, info->fbdefio->delay); |
---|
| 1319 | + schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY); |
---|
| 1320 | + |
---|
| 1321 | + /* 0 means do resume */ |
---|
| 1322 | + fb_set_suspend(info, 0); |
---|
| 1323 | + |
---|
| 1324 | +out: |
---|
| 1325 | + console_unlock(); |
---|
| 1326 | + |
---|
| 1327 | + return ret; |
---|
893 | 1328 | } |
---|
894 | 1329 | |
---|
895 | 1330 | |
---|
.. | .. |
---|
915 | 1350 | .id_table = id_table, |
---|
916 | 1351 | .probe = hvfb_probe, |
---|
917 | 1352 | .remove = hvfb_remove, |
---|
| 1353 | + .suspend = hvfb_suspend, |
---|
| 1354 | + .resume = hvfb_resume, |
---|
918 | 1355 | .driver = { |
---|
919 | 1356 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
---|
920 | 1357 | }, |
---|