ff06de
centosplus patch: centos-linux-3.10-hyperv_fb-67e7cdb4829d-bug18117.patch
ff06de
ff06de
commit 67e7cdb4829d3246c98f2ec9b771303ebe162eab
ff06de
Author: Wei Hu <weh@microsoft.com>
ff06de
Date:   Thu Sep 5 09:11:53 2019 +0000
ff06de
ff06de
    video: hyperv: hyperv_fb: Obtain screen resolution from Hyper-V host
ff06de
    
ff06de
    Beginning from Windows 10 RS5+, VM screen resolution is obtained from host.
ff06de
    The "video=hyperv_fb" boot time option is not needed, but still can be
ff06de
    used to overwrite what the host specifies. The VM resolution on the host
ff06de
    could be set by executing the powershell "set-vmvideo" command.
ff06de
    
ff06de
    Signed-off-by: Iouri Tarassov <iourit@microsoft.com>
ff06de
    Signed-off-by: Wei Hu <weh@microsoft.com>
ff06de
    Reviewed-by: Michael Kelley <mikelley@microsoft.com>
ff06de
    Reviewed-by: Dexuan Cui <decui@microsoft.com>
ff06de
    Signed-off-by: Sasha Levin <sashal@kernel.org>
ff06de
ff06de
    Applied-by: Akemi Yagi <toracat@centos.org>
ff06de
    (minor adjustments include commenting out the line "+case VERSION_WIN10_V5:
ff06de
     and removal of fbdev/ from the path)
ff06de
ff06de
diff --git a/drivers/video/hyperv_fb.c b/drivers/video/hyperv_fb.c
ff06de
index fe4731f..98b1ae9 100644
ff06de
--- a/drivers/video/hyperv_fb.c
ff06de
+++ b/drivers/video/hyperv_fb.c
ff06de
@@ -23,6 +23,14 @@
ff06de
  *
ff06de
  * Portrait orientation is also supported:
ff06de
  *     For example: video=hyperv_fb:864x1152
ff06de
+ *
ff06de
+ * When a Windows 10 RS5+ host is used, the virtual machine screen
ff06de
+ * resolution is obtained from the host. The "video=hyperv_fb" option is
ff06de
+ * not needed, but still can be used to overwrite what the host specifies.
ff06de
+ * The VM resolution on the host could be set by executing the powershell
ff06de
+ * "set-vmvideo" command. For example
ff06de
+ *     set-vmvideo -vmname name -horizontalresolution:1920 \
ff06de
+ * -verticalresolution:1200 -resolutiontype single
ff06de
  */
ff06de
 
ff06de
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
ff06de
@@ -45,6 +53,10 @@
ff06de
 #define SYNTHVID_VERSION(major, minor) ((minor) << 16 | (major))
ff06de
 #define SYNTHVID_VERSION_WIN7 SYNTHVID_VERSION(3, 0)
ff06de
 #define SYNTHVID_VERSION_WIN8 SYNTHVID_VERSION(3, 2)
ff06de
+#define SYNTHVID_VERSION_WIN10 SYNTHVID_VERSION(3, 5)
ff06de
+
ff06de
+#define SYNTHVID_VER_GET_MAJOR(ver) (ver & 0x0000ffff)
ff06de
+#define SYNTHVID_VER_GET_MINOR(ver) ((ver & 0xffff0000) >> 16)
ff06de
 
ff06de
 #define SYNTHVID_DEPTH_WIN7 16
ff06de
 #define SYNTHVID_DEPTH_WIN8 32
ff06de
@@ -83,16 +95,25 @@ enum synthvid_msg_type {
ff06de
 	SYNTHVID_POINTER_SHAPE		= 8,
ff06de
 	SYNTHVID_FEATURE_CHANGE		= 9,
ff06de
 	SYNTHVID_DIRT			= 10,
ff06de
+	SYNTHVID_RESOLUTION_REQUEST	= 13,
ff06de
+	SYNTHVID_RESOLUTION_RESPONSE	= 14,
ff06de
 
ff06de
-	SYNTHVID_MAX			= 11
ff06de
+	SYNTHVID_MAX			= 15
ff06de
 };
ff06de
 
ff06de
+#define		SYNTHVID_EDID_BLOCK_SIZE	128
ff06de
+#define		SYNTHVID_MAX_RESOLUTION_COUNT	64
ff06de
+
ff06de
+struct hvd_screen_info {
ff06de
+	u16 width;
ff06de
+	u16 height;
ff06de
+} __packed;
ff06de
+
ff06de
 struct synthvid_msg_hdr {
ff06de
 	u32 type;
ff06de
 	u32 size;  /* size of this header + payload after this field*/
ff06de
 } __packed;
ff06de
 
ff06de
-
ff06de
 struct synthvid_version_req {
ff06de
 	u32 version;
ff06de
 } __packed;
ff06de
@@ -103,6 +124,19 @@ struct synthvid_version_resp {
ff06de
 	u8 max_video_outputs;
ff06de
 } __packed;
ff06de
 
ff06de
+struct synthvid_supported_resolution_req {
ff06de
+	u8 maximum_resolution_count;
ff06de
+} __packed;
ff06de
+
ff06de
+struct synthvid_supported_resolution_resp {
ff06de
+	u8 edid_block[SYNTHVID_EDID_BLOCK_SIZE];
ff06de
+	u8 resolution_count;
ff06de
+	u8 default_resolution_index;
ff06de
+	u8 is_standard;
ff06de
+	struct hvd_screen_info
ff06de
+		supported_resolution[SYNTHVID_MAX_RESOLUTION_COUNT];
ff06de
+} __packed;
ff06de
+
ff06de
 struct synthvid_vram_location {
ff06de
 	u64 user_ctx;
ff06de
 	u8 is_vram_gpa_specified;
ff06de
@@ -188,6 +222,8 @@ struct synthvid_msg {
ff06de
 		struct synthvid_pointer_shape ptr_shape;
ff06de
 		struct synthvid_feature_change feature_chg;
ff06de
 		struct synthvid_dirt dirt;
ff06de
+		struct synthvid_supported_resolution_req resolution_req;
ff06de
+		struct synthvid_supported_resolution_resp resolution_resp;
ff06de
 	};
ff06de
 } __packed;
ff06de
 
ff06de
@@ -226,6 +262,8 @@ struct hvfb_par {
ff06de
 
ff06de
 static uint screen_width = HVFB_WIDTH;
ff06de
 static uint screen_height = HVFB_HEIGHT;
ff06de
+static uint screen_width_max = HVFB_WIDTH;
ff06de
+static uint screen_height_max = HVFB_HEIGHT;
ff06de
 static uint screen_depth;
ff06de
 static uint screen_fb_size;
ff06de
 
ff06de
@@ -356,6 +394,7 @@ static void synthvid_recv_sub(struct hv_device *hdev)
ff06de
 
ff06de
 	/* Complete the wait event */
ff06de
 	if (msg->vid_hdr.type == SYNTHVID_VERSION_RESPONSE ||
ff06de
+	    msg->vid_hdr.type == SYNTHVID_RESOLUTION_RESPONSE ||
ff06de
 	    msg->vid_hdr.type == SYNTHVID_VRAM_LOCATION_ACK) {
ff06de
 		memcpy(par->init_buf, msg, MAX_VMBUS_PKT_SIZE);
ff06de
 		complete(&par->wait);
ff06de
@@ -402,6 +441,17 @@ static void synthvid_receive(void *ctx)
ff06de
 	} while (bytes_recvd > 0 && ret == 0);
ff06de
 }
ff06de
 
ff06de
+/* Check if the ver1 version is equal or greater than ver2 */
ff06de
+static inline bool synthvid_ver_ge(u32 ver1, u32 ver2)
ff06de
+{
ff06de
+	if (SYNTHVID_VER_GET_MAJOR(ver1) > SYNTHVID_VER_GET_MAJOR(ver2) ||
ff06de
+	    (SYNTHVID_VER_GET_MAJOR(ver1) == SYNTHVID_VER_GET_MAJOR(ver2) &&
ff06de
+	     SYNTHVID_VER_GET_MINOR(ver1) >= SYNTHVID_VER_GET_MINOR(ver2)))
ff06de
+		return true;
ff06de
+
ff06de
+	return false;
ff06de
+}
ff06de
+
ff06de
 /* Check synthetic video protocol version with the host */
ff06de
 static int synthvid_negotiate_ver(struct hv_device *hdev, u32 ver)
ff06de
 {
ff06de
@@ -430,6 +480,64 @@ static int synthvid_negotiate_ver(struct hv_device *hdev, u32 ver)
ff06de
 	}
ff06de
 
ff06de
 	par->synthvid_version = ver;
ff06de
+	pr_info("Synthvid Version major %d, minor %d\n",
ff06de
+		SYNTHVID_VER_GET_MAJOR(ver), SYNTHVID_VER_GET_MINOR(ver));
ff06de
+
ff06de
+out:
ff06de
+	return ret;
ff06de
+}
ff06de
+
ff06de
+/* Get current resolution from the host */
ff06de
+static int synthvid_get_supported_resolution(struct hv_device *hdev)
ff06de
+{
ff06de
+	struct fb_info *info = hv_get_drvdata(hdev);
ff06de
+	struct hvfb_par *par = info->par;
ff06de
+	struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf;
ff06de
+	int ret = 0;
ff06de
+	unsigned long t;
ff06de
+	u8 index;
ff06de
+	int i;
ff06de
+
ff06de
+	memset(msg, 0, sizeof(struct synthvid_msg));
ff06de
+	msg->vid_hdr.type = SYNTHVID_RESOLUTION_REQUEST;
ff06de
+	msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
ff06de
+		sizeof(struct synthvid_supported_resolution_req);
ff06de
+
ff06de
+	msg->resolution_req.maximum_resolution_count =
ff06de
+		SYNTHVID_MAX_RESOLUTION_COUNT;
ff06de
+	synthvid_send(hdev, msg);
ff06de
+
ff06de
+	t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT);
ff06de
+	if (!t) {
ff06de
+		pr_err("Time out on waiting resolution response\n");
ff06de
+			ret = -ETIMEDOUT;
ff06de
+			goto out;
ff06de
+	}
ff06de
+
ff06de
+	if (msg->resolution_resp.resolution_count == 0) {
ff06de
+		pr_err("No supported resolutions\n");
ff06de
+		ret = -ENODEV;
ff06de
+		goto out;
ff06de
+	}
ff06de
+
ff06de
+	index = msg->resolution_resp.default_resolution_index;
ff06de
+	if (index >= msg->resolution_resp.resolution_count) {
ff06de
+		pr_err("Invalid resolution index: %d\n", index);
ff06de
+		ret = -ENODEV;
ff06de
+		goto out;
ff06de
+	}
ff06de
+
ff06de
+	for (i = 0; i < msg->resolution_resp.resolution_count; i++) {
ff06de
+		screen_width_max = max_t(unsigned int, screen_width_max,
ff06de
+		    msg->resolution_resp.supported_resolution[i].width);
ff06de
+		screen_height_max = max_t(unsigned int, screen_height_max,
ff06de
+		    msg->resolution_resp.supported_resolution[i].height);
ff06de
+	}
ff06de
+
ff06de
+	screen_width =
ff06de
+		msg->resolution_resp.supported_resolution[index].width;
ff06de
+	screen_height =
ff06de
+		msg->resolution_resp.supported_resolution[index].height;
ff06de
 
ff06de
 out:
ff06de
 	return ret;
ff06de
@@ -450,11 +558,27 @@ static int synthvid_connect_vsp(struct hv_device *hdev)
ff06de
 	}
ff06de
 
ff06de
 	/* Negotiate the protocol version with host */
ff06de
-	if (vmbus_proto_version == VERSION_WS2008 ||
ff06de
-	    vmbus_proto_version == VERSION_WIN7)
ff06de
-		ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN7);
ff06de
-	else
ff06de
+	switch (vmbus_proto_version) {
ff06de
+	case VERSION_WIN10:
ff06de
+	/* case VERSION_WIN10_V5: */
ff06de
+		ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN10);
ff06de
+		if (!ret)
ff06de
+			break;
ff06de
+		/* Fallthrough */
ff06de
+	case VERSION_WIN8:
ff06de
+	case VERSION_WIN8_1:
ff06de
 		ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN8);
ff06de
+		if (!ret)
ff06de
+			break;
ff06de
+		/* Fallthrough */
ff06de
+	case VERSION_WS2008:
ff06de
+	case VERSION_WIN7:
ff06de
+		ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN7);
ff06de
+		break;
ff06de
+	default:
ff06de
+		ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN10);
ff06de
+		break;
ff06de
+	}
ff06de
 
ff06de
 	if (ret) {
ff06de
 		pr_err("Synthetic video device version not accepted\n");
ff06de
@@ -466,6 +590,12 @@ static int synthvid_connect_vsp(struct hv_device *hdev)
ff06de
 	else
ff06de
 		screen_depth = SYNTHVID_DEPTH_WIN8;
ff06de
 
ff06de
+	if (synthvid_ver_ge(par->synthvid_version, SYNTHVID_VERSION_WIN10)) {
ff06de
+		ret = synthvid_get_supported_resolution(hdev);
ff06de
+		if (ret)
ff06de
+			pr_info("Failed to get supported resolution from host, use default\n");
ff06de
+	}
ff06de
+
ff06de
 	screen_fb_size = hdev->channel->offermsg.offer.
ff06de
 				mmio_megabytes * 1024 * 1024;
ff06de
 
ff06de
@@ -655,6 +785,8 @@ static void hvfb_get_option(struct fb_info *info)
ff06de
 	}
ff06de
 
ff06de
 	if (x < HVFB_WIDTH_MIN || y < HVFB_HEIGHT_MIN ||
ff06de
+	    (synthvid_ver_ge(par->synthvid_version, SYNTHVID_VERSION_WIN10) &&
ff06de
+	    (x > screen_width_max || y > screen_height_max)) ||
ff06de
 	    (par->synthvid_version == SYNTHVID_VERSION_WIN8 &&
ff06de
 	     x * y * screen_depth / 8 > SYNTHVID_FB_SIZE_WIN8) ||
ff06de
 	    (par->synthvid_version == SYNTHVID_VERSION_WIN7 &&
ff06de
@@ -691,8 +823,12 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
ff06de
 		}
ff06de
 
ff06de
 		if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) ||
ff06de
-		    pci_resource_len(pdev, 0) < screen_fb_size)
ff06de
+		    pci_resource_len(pdev, 0) < screen_fb_size) {
ff06de
+			pr_err("Resource not available or (0x%lx < 0x%lx)\n",
ff06de
+			       (unsigned long) pci_resource_len(pdev, 0),
ff06de
+			       (unsigned long) screen_fb_size);
ff06de
 			goto err1;
ff06de
+		}
ff06de
 
ff06de
 		pot_end = pci_resource_end(pdev, 0);
ff06de
 		pot_start = pot_end - screen_fb_size + 1;
ff06de
@@ -781,17 +917,16 @@ static int hvfb_probe(struct hv_device *hdev,
ff06de
 		goto error1;
ff06de
 	}
ff06de
 
ff06de
+	hvfb_get_option(info);
ff06de
+	pr_info("Screen resolution: %dx%d, Color depth: %d\n",
ff06de
+		screen_width, screen_height, screen_depth);
ff06de
+
ff06de
 	ret = hvfb_getmem(hdev, info);
ff06de
 	if (ret) {
ff06de
 		pr_err("No memory for framebuffer\n");
ff06de
 		goto error2;
ff06de
 	}
ff06de
 
ff06de
-	hvfb_get_option(info);
ff06de
-	pr_info("Screen resolution: %dx%d, Color depth: %d\n",
ff06de
-		screen_width, screen_height, screen_depth);
ff06de
-
ff06de
-
ff06de
 	/* Set up fb_info */
ff06de
 	info->flags = FBINFO_DEFAULT;
ff06de