summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/include/ipxe/vmbus.h14
-rw-r--r--src/interface/hyperv/vmbus.c86
2 files changed, 83 insertions, 17 deletions
diff --git a/src/include/ipxe/vmbus.h b/src/include/ipxe/vmbus.h
index 1ea65edf..8725ee6a 100644
--- a/src/include/ipxe/vmbus.h
+++ b/src/include/ipxe/vmbus.h
@@ -42,8 +42,17 @@ union vmbus_version {
};
} __attribute__ (( packed ));
-/** Oldest known VMBus protocol version (Windows Server 2008) */
-#define VMBUS_VERSION_WS2008 ( ( 0 << 16 ) | ( 13 << 0 ) )
+/** Known VMBus protocol versions */
+enum vmbus_raw_version {
+ /** Windows Server 2008 */
+ VMBUS_VERSION_WS2008 = ( ( 0 << 16 ) | ( 13 << 0 ) ),
+ /** Windows 7 */
+ VMBUS_VERSION_WIN7 = ( ( 1 << 16 ) | ( 1 << 0 ) ),
+ /** Windows 8 */
+ VMBUS_VERSION_WIN8 = ( ( 2 << 16 ) | ( 4 << 0 ) ),
+ /** Windows 8.1 */
+ VMBUS_VERSION_WIN8_1 = ( ( 3 << 16 ) | ( 0 << 0 ) ),
+};
/** Guest physical address range descriptor */
struct vmbus_gpa_range {
@@ -82,6 +91,7 @@ enum vmbus_message_type {
VMBUS_INITIATE_CONTACT = 14,
VMBUS_VERSION_RESPONSE = 15,
VMBUS_UNLOAD = 16,
+ VMBUS_UNLOAD_RESPONSE = 17,
};
/** VMBus "offer channel" message */
diff --git a/src/interface/hyperv/vmbus.c b/src/interface/hyperv/vmbus.c
index 79fa3e5e..9c18285d 100644
--- a/src/interface/hyperv/vmbus.c
+++ b/src/interface/hyperv/vmbus.c
@@ -38,13 +38,6 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/hyperv.h>
#include <ipxe/vmbus.h>
-/** Chosen VMBus protocol version
- *
- * This is a policy decision. We use the oldest common version in
- * order to avoid including any version-specific code.
- */
-#define VMBUS_VERSION VMBUS_VERSION_WS2008
-
/** VMBus initial GPADL ID
*
* 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 ) {
* Initiate contact
*
* @v hv Hyper-V hypervisor
+ * @v raw VMBus protocol (raw) version
* @ret rc Return status code
*/
-static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
+static int vmbus_initiate_contact ( struct hv_hypervisor *hv,
+ unsigned int raw ) {
struct vmbus *vmbus = hv->vmbus;
const struct vmbus_version_response *version = &vmbus->message->version;
struct vmbus_initiate_contact initiate;
@@ -133,7 +128,7 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
/* Construct message */
memset ( &initiate, 0, sizeof ( initiate ) );
initiate.header.type = cpu_to_le32 ( VMBUS_INITIATE_CONTACT );
- initiate.version.raw = cpu_to_le32 ( VMBUS_VERSION );
+ initiate.version.raw = cpu_to_le32 ( raw );
initiate.intr = virt_to_phys ( vmbus->intr );
initiate.monitor_in = virt_to_phys ( vmbus->monitor_in );
initiate.monitor_out = virt_to_phys ( vmbus->monitor_out );
@@ -158,7 +153,7 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
vmbus );
return -ENOTSUP;
}
- if ( version->version.raw != cpu_to_le32 ( VMBUS_VERSION ) ) {
+ if ( version->version.raw != cpu_to_le32 ( raw ) ) {
DBGC ( vmbus, "VMBUS %p unexpected version %d.%d\n",
vmbus, le16_to_cpu ( version->version.major ),
le16_to_cpu ( version->version.minor ) );
@@ -178,8 +173,69 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
* @ret rc Return status code
*/
static int vmbus_unload ( struct hv_hypervisor *hv ) {
+ struct vmbus *vmbus = hv->vmbus;
+ const struct vmbus_message_header *header = &vmbus->message->header;
+ int rc;
+
+ /* Post message */
+ if ( ( rc = vmbus_post_empty_message ( hv, VMBUS_UNLOAD ) ) != 0 )
+ return rc;
+
+ /* Wait for response */
+ if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 )
+ return rc;
+
+ /* Check response */
+ if ( header->type != cpu_to_le32 ( VMBUS_UNLOAD_RESPONSE ) ) {
+ DBGC ( vmbus, "VMBUS %p unexpected unload response type %d\n",
+ vmbus, le32_to_cpu ( header->type ) );
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+/**
+ * Negotiate protocol version
+ *
+ * @v hv Hyper-V hypervisor
+ * @ret rc Return status code
+ */
+static int vmbus_negotiate_version ( struct hv_hypervisor *hv ) {
+ int rc;
- return vmbus_post_empty_message ( hv, VMBUS_UNLOAD );
+ /* 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.
+ */
+
+ /* Initiate contact to check for required protocol support */
+ if ( ( rc = vmbus_initiate_contact ( hv, VMBUS_VERSION_WIN8_1 ) ) != 0 )
+ return rc;
+
+ /* Terminate contact */
+ if ( ( rc = vmbus_unload ( hv ) ) != 0 )
+ return rc;
+
+ /* Reinitiate contact using the oldest known protocol version */
+ if ( ( rc = vmbus_initiate_contact ( hv, VMBUS_VERSION_WS2008 ) ) != 0 )
+ return rc;
+
+ return 0;
}
/**
@@ -1232,9 +1288,9 @@ int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent ) {
/* Enable message interrupt */
hv_enable_sint ( hv, VMBUS_MESSAGE_SINT );
- /* Initiate contact */
- if ( ( rc = vmbus_initiate_contact ( hv ) ) != 0 )
- goto err_initiate_contact;
+ /* Negotiate protocol version */
+ if ( ( rc = vmbus_negotiate_version ( hv ) ) != 0 )
+ goto err_negotiate_version;
/* Enumerate channels */
if ( ( rc = vmbus_probe_channels ( hv, parent ) ) != 0 )
@@ -1245,7 +1301,7 @@ int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent ) {
vmbus_remove_channels ( hv, parent );
err_probe_channels:
vmbus_unload ( hv );
- err_initiate_contact:
+ err_negotiate_version:
hv_disable_sint ( hv, VMBUS_MESSAGE_SINT );
hv_free_pages ( hv, vmbus->intr, vmbus->monitor_in, vmbus->monitor_out,
NULL );