瀏覽代碼

[hyperv] Require support for VMBus version 3.0 or newer

We require the ability to disconnect from and reconnect to VMBus; if
we don't have this then there is no (viable) way for a loaded
operating system to continue to use any VMBus devices.  (There is also
a small but non-zero risk that the host will continue to write to our
interrupt and monitor pages, since the VMBUS_UNLOAD message in earlier
versions is essentially a no-op.)

This requires us to ensure that the host supports protocol version 3.0
(VMBUS_VERSION_WIN8_1).  However, we can't actually _use_ protocol
version 3.0, since doing so causes an iSCSI-booted Windows Server 2012
R2 VM to crash due to a NULL pointer dereference in vmbus.sys.

To work around this problem, we first ensure that we can connect using
protocol v3.0, then disconnect and reconnect using the oldest known
protocol.

This deliberately prevents the use of the iPXE native Hyper-V drivers
on older versions of Hyper-V, where we could use our drivers but in so
doing would break the loaded operating system.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 9 年之前
父節點
當前提交
0166a68351
共有 2 個檔案被更改,包括 83 行新增17 行删除
  1. 12
    2
      src/include/ipxe/vmbus.h
  2. 71
    15
      src/interface/hyperv/vmbus.c

+ 12
- 2
src/include/ipxe/vmbus.h 查看文件

@@ -42,8 +42,17 @@ union vmbus_version {
42 42
 	};
43 43
 } __attribute__ (( packed ));
44 44
 
45
-/** Oldest known VMBus protocol version (Windows Server 2008) */
46
-#define VMBUS_VERSION_WS2008 ( ( 0 << 16 ) | ( 13 << 0 ) )
45
+/** Known VMBus protocol versions */
46
+enum vmbus_raw_version {
47
+	/** Windows Server 2008 */
48
+	VMBUS_VERSION_WS2008 = ( ( 0 << 16 ) | ( 13 << 0 ) ),
49
+	/** Windows 7 */
50
+	VMBUS_VERSION_WIN7 = ( ( 1 << 16 ) | ( 1 << 0 ) ),
51
+	/** Windows 8 */
52
+	VMBUS_VERSION_WIN8 = ( ( 2 << 16 ) | ( 4 << 0 ) ),
53
+	/** Windows 8.1 */
54
+	VMBUS_VERSION_WIN8_1 = ( ( 3 << 16 ) | ( 0 << 0 ) ),
55
+};
47 56
 
48 57
 /** Guest physical address range descriptor */
49 58
 struct vmbus_gpa_range {
@@ -82,6 +91,7 @@ enum vmbus_message_type {
82 91
 	VMBUS_INITIATE_CONTACT = 14,
83 92
 	VMBUS_VERSION_RESPONSE = 15,
84 93
 	VMBUS_UNLOAD = 16,
94
+	VMBUS_UNLOAD_RESPONSE = 17,
85 95
 };
86 96
 
87 97
 /** VMBus "offer channel" message */

+ 71
- 15
src/interface/hyperv/vmbus.c 查看文件

@@ -38,13 +38,6 @@ FILE_LICENCE ( GPL2_OR_LATER );
38 38
 #include <ipxe/hyperv.h>
39 39
 #include <ipxe/vmbus.h>
40 40
 
41
-/** Chosen VMBus protocol version
42
- *
43
- * This is a policy decision.  We use the oldest common version in
44
- * order to avoid including any version-specific code.
45
- */
46
-#define VMBUS_VERSION VMBUS_VERSION_WS2008
47
-
48 41
 /** VMBus initial GPADL ID
49 42
  *
50 43
  * This is an opaque value with no meaning.  The Linux kernel uses
@@ -122,9 +115,11 @@ static int vmbus_wait_for_message ( struct hv_hypervisor *hv ) {
122 115
  * Initiate contact
123 116
  *
124 117
  * @v hv		Hyper-V hypervisor
118
+ * @v raw		VMBus protocol (raw) version
125 119
  * @ret rc		Return status code
126 120
  */
127
-static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
121
+static int vmbus_initiate_contact ( struct hv_hypervisor *hv,
122
+				    unsigned int raw ) {
128 123
 	struct vmbus *vmbus = hv->vmbus;
129 124
 	const struct vmbus_version_response *version = &vmbus->message->version;
130 125
 	struct vmbus_initiate_contact initiate;
@@ -133,7 +128,7 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
133 128
 	/* Construct message */
134 129
 	memset ( &initiate, 0, sizeof ( initiate ) );
135 130
 	initiate.header.type = cpu_to_le32 ( VMBUS_INITIATE_CONTACT );
136
-	initiate.version.raw = cpu_to_le32 ( VMBUS_VERSION );
131
+	initiate.version.raw = cpu_to_le32 ( raw );
137 132
 	initiate.intr = virt_to_phys ( vmbus->intr );
138 133
 	initiate.monitor_in = virt_to_phys ( vmbus->monitor_in );
139 134
 	initiate.monitor_out = virt_to_phys ( vmbus->monitor_out );
@@ -158,7 +153,7 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
158 153
 		       vmbus );
159 154
 		return -ENOTSUP;
160 155
 	}
161
-	if ( version->version.raw != cpu_to_le32 ( VMBUS_VERSION ) ) {
156
+	if ( version->version.raw != cpu_to_le32 ( raw ) ) {
162 157
 		DBGC ( vmbus, "VMBUS %p unexpected version %d.%d\n",
163 158
 		       vmbus, le16_to_cpu ( version->version.major ),
164 159
 		       le16_to_cpu ( version->version.minor ) );
@@ -178,8 +173,69 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
178 173
  * @ret rc		Return status code
179 174
  */
180 175
 static int vmbus_unload ( struct hv_hypervisor *hv ) {
176
+	struct vmbus *vmbus = hv->vmbus;
177
+	const struct vmbus_message_header *header = &vmbus->message->header;
178
+	int rc;
179
+
180
+	/* Post message */
181
+	if ( ( rc = vmbus_post_empty_message ( hv, VMBUS_UNLOAD ) ) != 0 )
182
+		return rc;
183
+
184
+	/* Wait for response */
185
+	if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 )
186
+		return rc;
187
+
188
+	/* Check response */
189
+	if ( header->type != cpu_to_le32 ( VMBUS_UNLOAD_RESPONSE ) ) {
190
+		DBGC ( vmbus, "VMBUS %p unexpected unload response type %d\n",
191
+		       vmbus, le32_to_cpu ( header->type ) );
192
+		return -EPROTO;
193
+	}
194
+
195
+	return 0;
196
+}
197
+
198
+/**
199
+ * Negotiate protocol version
200
+ *
201
+ * @v hv		Hyper-V hypervisor
202
+ * @ret rc		Return status code
203
+ */
204
+static int vmbus_negotiate_version ( struct hv_hypervisor *hv ) {
205
+	int rc;
181 206
 
182
-	return vmbus_post_empty_message ( hv, VMBUS_UNLOAD );
207
+	/* We require the ability to disconnect from and reconnect to
208
+	 * VMBus; if we don't have this then there is no (viable) way
209
+	 * for a loaded operating system to continue to use any VMBus
210
+	 * devices.  (There is also a small but non-zero risk that the
211
+	 * host will continue to write to our interrupt and monitor
212
+	 * pages, since the VMBUS_UNLOAD message in earlier versions
213
+	 * is essentially a no-op.)
214
+	 *
215
+	 * This requires us to ensure that the host supports protocol
216
+	 * version 3.0 (VMBUS_VERSION_WIN8_1).  However, we can't
217
+	 * actually _use_ protocol version 3.0, since doing so causes
218
+	 * an iSCSI-booted Windows Server 2012 R2 VM to crash due to a
219
+	 * NULL pointer dereference in vmbus.sys.
220
+	 *
221
+	 * To work around this problem, we first ensure that we can
222
+	 * connect using protocol v3.0, then disconnect and reconnect
223
+	 * using the oldest known protocol.
224
+	 */
225
+
226
+	/* Initiate contact to check for required protocol support */
227
+	if ( ( rc = vmbus_initiate_contact ( hv, VMBUS_VERSION_WIN8_1 ) ) != 0 )
228
+		return rc;
229
+
230
+	/* Terminate contact */
231
+	if ( ( rc = vmbus_unload ( hv ) ) != 0 )
232
+		return rc;
233
+
234
+	/* Reinitiate contact using the oldest known protocol version */
235
+	if ( ( rc = vmbus_initiate_contact ( hv, VMBUS_VERSION_WS2008 ) ) != 0 )
236
+		return rc;
237
+
238
+	return 0;
183 239
 }
184 240
 
185 241
 /**
@@ -1232,9 +1288,9 @@ int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent ) {
1232 1288
 	/* Enable message interrupt */
1233 1289
 	hv_enable_sint ( hv, VMBUS_MESSAGE_SINT );
1234 1290
 
1235
-	/* Initiate contact */
1236
-	if ( ( rc = vmbus_initiate_contact ( hv ) ) != 0 )
1237
-		goto err_initiate_contact;
1291
+	/* Negotiate protocol version */
1292
+	if ( ( rc = vmbus_negotiate_version ( hv ) ) != 0 )
1293
+		goto err_negotiate_version;
1238 1294
 
1239 1295
 	/* Enumerate channels */
1240 1296
 	if ( ( rc = vmbus_probe_channels ( hv, parent ) ) != 0 )
@@ -1245,7 +1301,7 @@ int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent ) {
1245 1301
 	vmbus_remove_channels ( hv, parent );
1246 1302
  err_probe_channels:
1247 1303
 	vmbus_unload ( hv );
1248
- err_initiate_contact:
1304
+ err_negotiate_version:
1249 1305
 	hv_disable_sint ( hv, VMBUS_MESSAGE_SINT );
1250 1306
 	hv_free_pages ( hv, vmbus->intr, vmbus->monitor_in, vmbus->monitor_out,
1251 1307
 			NULL );

Loading…
取消
儲存