summaryrefslogtreecommitdiffstats
path: root/src/interface
diff options
context:
space:
mode:
authorMichael Brown2017-04-25 15:13:22 +0200
committerMichael Brown2017-04-28 17:20:47 +0200
commitb91cc983da48b2791a672431551f7859e33126ec (patch)
tree466bc46ef18d86a619769a49acf4c149193237b1 /src/interface
parent[hyperv] Remove redundant return status code from mapping functions (diff)
downloadipxe-b91cc983da48b2791a672431551f7859e33126ec.tar.gz
ipxe-b91cc983da48b2791a672431551f7859e33126ec.tar.xz
ipxe-b91cc983da48b2791a672431551f7859e33126ec.zip
[hyperv] Cope with Windows Server 2016 enlightenments
An "enlightened" external bootloader (such as Windows Server 2016's winload.exe) may take ownership of the Hyper-V connection before all INT 13 operations have been completed. When this happens, all VMBus devices are implicitly closed and we are left with a non-functional network connection. Detect when our Hyper-V connection has been lost (by checking the SynIC message page MSR). Reclaim ownership of the Hyper-V connection and reestablish any VMBus devices, without disrupting any existing iPXE state (such as IPv4 settings attached to the network device). Windows Server 2016 will not cleanly take ownership of an active Hyper-V connection. Experimentation shows that we can quiesce by resetting only the SynIC message page MSR; this results in a successful SAN boot (on a Windows 2012 R2 physical host). Choose to quiesce by resetting (almost) all MSRs, in the hope that this will be more robust against corner cases such as a stray synthetic interrupt occurring during the handover. Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/interface')
-rw-r--r--src/interface/hyperv/vmbus.c133
1 files changed, 129 insertions, 4 deletions
diff --git a/src/interface/hyperv/vmbus.c b/src/interface/hyperv/vmbus.c
index 7915ddfe..45a7caec 100644
--- a/src/interface/hyperv/vmbus.c
+++ b/src/interface/hyperv/vmbus.c
@@ -50,6 +50,16 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*/
#define VMBUS_GPADL_MAGIC 0x18ae0000
+/** Current (i.e. most recently issued) GPADL ID */
+static unsigned int vmbus_gpadl = VMBUS_GPADL_MAGIC;
+
+/** Obsolete GPADL ID threshold
+ *
+ * When the Hyper-V connection is reset, any previous GPADLs are
+ * automatically rendered obsolete.
+ */
+unsigned int vmbus_obsolete_gpadl;
+
/**
* Post message
*
@@ -281,12 +291,12 @@ int vmbus_establish_gpadl ( struct vmbus_device *vmdev, userptr_t data,
uint64_t pfn[pfn_count];
} __attribute__ (( packed )) gpadlhdr;
const struct vmbus_gpadl_created *created = &vmbus->message->created;
- static unsigned int gpadl = VMBUS_GPADL_MAGIC;
+ unsigned int gpadl;
unsigned int i;
int rc;
/* Allocate GPADL ID */
- gpadl++;
+ gpadl = ++vmbus_gpadl;
/* Construct message */
memset ( &gpadlhdr, 0, sizeof ( gpadlhdr ) );
@@ -347,6 +357,15 @@ int vmbus_gpadl_teardown ( struct vmbus_device *vmdev, unsigned int gpadl ) {
const struct vmbus_gpadl_torndown *torndown = &vmbus->message->torndown;
int rc;
+ /* If GPADL is obsolete (i.e. was created before the most
+ * recent Hyper-V reset), then we will never receive a
+ * response to the teardown message. Since the GPADL is
+ * already destroyed as far as the hypervisor is concerned, no
+ * further action is required.
+ */
+ if ( vmbus_gpadl_is_obsolete ( gpadl ) )
+ return 0;
+
/* Construct message */
memset ( &teardown, 0, sizeof ( teardown ) );
teardown.header.type = cpu_to_le32 ( VMBUS_GPADL_TEARDOWN );
@@ -530,8 +549,7 @@ void vmbus_close ( struct vmbus_device *vmdev ) {
}
/* Tear down GPADL */
- if ( ( rc = vmbus_gpadl_teardown ( vmdev,
- vmdev->gpadl ) ) != 0 ) {
+ if ( ( rc = vmbus_gpadl_teardown ( vmdev, vmdev->gpadl ) ) != 0 ) {
DBGC ( vmdev, "VMBUS %s failed to tear down channel GPADL: "
"%s\n", vmdev->dev.name, strerror ( rc ) );
/* We can't prevent the remote VM from continuing to
@@ -1187,6 +1205,8 @@ static int vmbus_probe_channels ( struct hv_hypervisor *hv,
&parent->children );
vmdev->dev.parent = parent;
vmdev->hv = hv;
+ memcpy ( &vmdev->instance, &offer->instance,
+ sizeof ( vmdev->instance ) );
vmdev->channel = channel;
vmdev->monitor = offer->monitor;
vmdev->signal = ( offer->monitored ?
@@ -1201,6 +1221,7 @@ static int vmbus_probe_channels ( struct hv_hypervisor *hv,
} else if ( header->type ==
cpu_to_le32 ( VMBUS_ALL_OFFERS_DELIVERED ) ) {
+ /* End of offer list */
break;
} else {
@@ -1244,6 +1265,77 @@ static int vmbus_probe_channels ( struct hv_hypervisor *hv,
return rc;
}
+
+/**
+ * Reset channels
+ *
+ * @v hv Hyper-V hypervisor
+ * @v parent Parent device
+ * @ret rc Return status code
+ */
+static int vmbus_reset_channels ( struct hv_hypervisor *hv,
+ struct device *parent ) {
+ struct vmbus *vmbus = hv->vmbus;
+ const struct vmbus_message_header *header = &vmbus->message->header;
+ const struct vmbus_offer_channel *offer = &vmbus->message->offer;
+ const union uuid *type;
+ struct vmbus_device *vmdev;
+ unsigned int channel;
+ int rc;
+
+ /* Post message */
+ if ( ( rc = vmbus_post_empty_message ( hv, VMBUS_REQUEST_OFFERS ) ) !=0)
+ return rc;
+
+ /* Collect responses */
+ while ( 1 ) {
+
+ /* Wait for response */
+ if ( ( rc = vmbus_wait_for_any_message ( hv ) ) != 0 )
+ return rc;
+
+ /* Handle response */
+ if ( header->type == cpu_to_le32 ( VMBUS_OFFER_CHANNEL ) ) {
+
+ /* Parse offer */
+ type = &offer->type;
+ channel = le32_to_cpu ( offer->channel );
+ DBGC2 ( vmbus, "VMBUS %p offer %d type %s",
+ vmbus, channel, uuid_ntoa ( type ) );
+ if ( offer->monitored )
+ DBGC2 ( vmbus, " monitor %d", offer->monitor );
+ DBGC2 ( vmbus, "\n" );
+
+ /* Do nothing with the offer; we already have all
+ * of the relevant state from the initial probe.
+ */
+
+ } else if ( header->type ==
+ cpu_to_le32 ( VMBUS_ALL_OFFERS_DELIVERED ) ) {
+
+ /* End of offer list */
+ break;
+
+ } else {
+ DBGC ( vmbus, "VMBUS %p unexpected offer response type "
+ "%d\n", vmbus, le32_to_cpu ( header->type ) );
+ return -EPROTO;
+ }
+ }
+
+ /* Reset all devices */
+ list_for_each_entry ( vmdev, &parent->children, dev.siblings ) {
+ if ( ( rc = vmdev->driver->reset ( vmdev ) ) != 0 ) {
+ DBGC ( vmdev, "VMBUS %s could not reset: %s\n",
+ vmdev->dev.name, strerror ( rc ) );
+ /* Continue attempting to reset other devices */
+ continue;
+ }
+ }
+
+ return 0;
+}
+
/**
* Remove channels
*
@@ -1331,6 +1423,39 @@ int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent ) {
}
/**
+ * Reset Hyper-V virtual machine bus
+ *
+ * @v hv Hyper-V hypervisor
+ * @v parent Parent device
+ * @ret rc Return status code
+ */
+int vmbus_reset ( struct hv_hypervisor *hv, struct device *parent ) {
+ struct vmbus *vmbus = hv->vmbus;
+ int rc;
+
+ /* Mark all existent GPADLs as obsolete */
+ vmbus_obsolete_gpadl = vmbus_gpadl;
+
+ /* Clear interrupt and monitor pages */
+ memset ( vmbus->intr, 0, PAGE_SIZE );
+ memset ( vmbus->monitor_in, 0, PAGE_SIZE );
+ memset ( vmbus->monitor_out, 0, PAGE_SIZE );
+
+ /* Enable message interrupt */
+ hv_enable_sint ( hv, VMBUS_MESSAGE_SINT );
+
+ /* Renegotiate protocol version */
+ if ( ( rc = vmbus_negotiate_version ( hv ) ) != 0 )
+ return rc;
+
+ /* Reenumerate channels */
+ if ( ( rc = vmbus_reset_channels ( hv, parent ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
* Remove Hyper-V virtual machine bus
*
* @v hv Hyper-V hypervisor