diff options
Diffstat (limited to 'drivers/hv/hv.c')
| -rw-r--r-- | drivers/hv/hv.c | 152 | 
1 files changed, 129 insertions, 23 deletions
| diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index f202ac7f4b3d..e83507f49676 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -13,9 +13,10 @@  #include <linux/slab.h>  #include <linux/vmalloc.h>  #include <linux/hyperv.h> -#include <linux/version.h>  #include <linux/random.h>  #include <linux/clockchips.h> +#include <linux/delay.h> +#include <linux/interrupt.h>  #include <clocksource/hyperv_timer.h>  #include <asm/mshyperv.h>  #include "hyperv_vmbus.h" @@ -37,6 +38,42 @@ int hv_init(void)  }  /* + * Functions for allocating and freeing memory with size and + * alignment HV_HYP_PAGE_SIZE. These functions are needed because + * the guest page size may not be the same as the Hyper-V page + * size. We depend upon kmalloc() aligning power-of-two size + * allocations to the allocation size boundary, so that the + * allocated memory appears to Hyper-V as a page of the size + * it expects. + */ + +void *hv_alloc_hyperv_page(void) +{ +	BUILD_BUG_ON(PAGE_SIZE <  HV_HYP_PAGE_SIZE); + +	if (PAGE_SIZE == HV_HYP_PAGE_SIZE) +		return (void *)__get_free_page(GFP_KERNEL); +	else +		return kmalloc(HV_HYP_PAGE_SIZE, GFP_KERNEL); +} + +void *hv_alloc_hyperv_zeroed_page(void) +{ +	if (PAGE_SIZE == HV_HYP_PAGE_SIZE) +		return (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO); +	else +		return kzalloc(HV_HYP_PAGE_SIZE, GFP_KERNEL); +} + +void hv_free_hyperv_page(unsigned long addr) +{ +	if (PAGE_SIZE == HV_HYP_PAGE_SIZE) +		free_page(addr); +	else +		kfree((void *)addr); +} + +/*   * hv_post_message - Post a message using the hypervisor message IPC.   *   * This involves a hypercall. @@ -68,7 +105,7 @@ int hv_post_message(union hv_connection_id connection_id,  	 */  	put_cpu_ptr(hv_cpu); -	return status & 0xFFFF; +	return hv_result(status);  }  int hv_synic_alloc(void) @@ -162,34 +199,48 @@ void hv_synic_enable_regs(unsigned int cpu)  	union hv_synic_scontrol sctrl;  	/* Setup the Synic's message page */ -	hv_get_simp(simp.as_uint64); +	simp.as_uint64 = hv_get_register(HV_REGISTER_SIMP);  	simp.simp_enabled = 1;  	simp.base_simp_gpa = virt_to_phys(hv_cpu->synic_message_page)  		>> HV_HYP_PAGE_SHIFT; -	hv_set_simp(simp.as_uint64); +	hv_set_register(HV_REGISTER_SIMP, simp.as_uint64);  	/* Setup the Synic's event page */ -	hv_get_siefp(siefp.as_uint64); +	siefp.as_uint64 = hv_get_register(HV_REGISTER_SIEFP);  	siefp.siefp_enabled = 1;  	siefp.base_siefp_gpa = virt_to_phys(hv_cpu->synic_event_page)  		>> HV_HYP_PAGE_SHIFT; -	hv_set_siefp(siefp.as_uint64); +	hv_set_register(HV_REGISTER_SIEFP, siefp.as_uint64);  	/* Setup the shared SINT. */ -	hv_get_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64); +	if (vmbus_irq != -1) +		enable_percpu_irq(vmbus_irq, 0); +	shared_sint.as_uint64 = hv_get_register(HV_REGISTER_SINT0 + +					VMBUS_MESSAGE_SINT); -	shared_sint.vector = hv_get_vector(); +	shared_sint.vector = vmbus_interrupt;  	shared_sint.masked = false; -	shared_sint.auto_eoi = hv_recommend_using_aeoi(); -	hv_set_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64); + +	/* +	 * On architectures where Hyper-V doesn't support AEOI (e.g., ARM64), +	 * it doesn't provide a recommendation flag and AEOI must be disabled. +	 */ +#ifdef HV_DEPRECATING_AEOI_RECOMMENDED +	shared_sint.auto_eoi = +			!(ms_hyperv.hints & HV_DEPRECATING_AEOI_RECOMMENDED); +#else +	shared_sint.auto_eoi = 0; +#endif +	hv_set_register(HV_REGISTER_SINT0 + VMBUS_MESSAGE_SINT, +				shared_sint.as_uint64);  	/* Enable the global synic bit */ -	hv_get_synic_state(sctrl.as_uint64); +	sctrl.as_uint64 = hv_get_register(HV_REGISTER_SCONTROL);  	sctrl.enable = 1; -	hv_set_synic_state(sctrl.as_uint64); +	hv_set_register(HV_REGISTER_SCONTROL, sctrl.as_uint64);  }  int hv_synic_init(unsigned int cpu) @@ -211,30 +262,71 @@ void hv_synic_disable_regs(unsigned int cpu)  	union hv_synic_siefp siefp;  	union hv_synic_scontrol sctrl; -	hv_get_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64); +	shared_sint.as_uint64 = hv_get_register(HV_REGISTER_SINT0 + +					VMBUS_MESSAGE_SINT);  	shared_sint.masked = 1;  	/* Need to correctly cleanup in the case of SMP!!! */  	/* Disable the interrupt */ -	hv_set_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64); +	hv_set_register(HV_REGISTER_SINT0 + VMBUS_MESSAGE_SINT, +				shared_sint.as_uint64); -	hv_get_simp(simp.as_uint64); +	simp.as_uint64 = hv_get_register(HV_REGISTER_SIMP);  	simp.simp_enabled = 0;  	simp.base_simp_gpa = 0; -	hv_set_simp(simp.as_uint64); +	hv_set_register(HV_REGISTER_SIMP, simp.as_uint64); -	hv_get_siefp(siefp.as_uint64); +	siefp.as_uint64 = hv_get_register(HV_REGISTER_SIEFP);  	siefp.siefp_enabled = 0;  	siefp.base_siefp_gpa = 0; -	hv_set_siefp(siefp.as_uint64); +	hv_set_register(HV_REGISTER_SIEFP, siefp.as_uint64);  	/* Disable the global synic bit */ -	hv_get_synic_state(sctrl.as_uint64); +	sctrl.as_uint64 = hv_get_register(HV_REGISTER_SCONTROL);  	sctrl.enable = 0; -	hv_set_synic_state(sctrl.as_uint64); +	hv_set_register(HV_REGISTER_SCONTROL, sctrl.as_uint64); + +	if (vmbus_irq != -1) +		disable_percpu_irq(vmbus_irq); +} + +#define HV_MAX_TRIES 3 +/* + * Scan the event flags page of 'this' CPU looking for any bit that is set.  If we find one + * bit set, then wait for a few milliseconds.  Repeat these steps for a maximum of 3 times. + * Return 'true', if there is still any set bit after this operation; 'false', otherwise. + * + * If a bit is set, that means there is a pending channel interrupt.  The expectation is + * that the normal interrupt handling mechanism will find and process the channel interrupt + * "very soon", and in the process clear the bit. + */ +static bool hv_synic_event_pending(void) +{ +	struct hv_per_cpu_context *hv_cpu = this_cpu_ptr(hv_context.cpu_context); +	union hv_synic_event_flags *event = +		(union hv_synic_event_flags *)hv_cpu->synic_event_page + VMBUS_MESSAGE_SINT; +	unsigned long *recv_int_page = event->flags; /* assumes VMBus version >= VERSION_WIN8 */ +	bool pending; +	u32 relid; +	int tries = 0; + +retry: +	pending = false; +	for_each_set_bit(relid, recv_int_page, HV_EVENT_FLAGS_COUNT) { +		/* Special case - VMBus channel protocol messages */ +		if (relid == 0) +			continue; +		pending = true; +		break; +	} +	if (pending && tries++ < HV_MAX_TRIES) { +		usleep_range(10000, 20000); +		goto retry; +	} +	return pending;  }  int hv_synic_cleanup(unsigned int cpu) @@ -242,6 +334,9 @@ int hv_synic_cleanup(unsigned int cpu)  	struct vmbus_channel *channel, *sc;  	bool channel_found = false; +	if (vmbus_connection.conn_state != CONNECTED) +		goto always_cleanup; +  	/*  	 * Hyper-V does not provide a way to change the connect CPU once  	 * it is set; we must prevent the connect CPU from going offline @@ -249,8 +344,7 @@ int hv_synic_cleanup(unsigned int cpu)  	 * path where the vmbus is already disconnected, the CPU must be  	 * allowed to shut down.  	 */ -	if (cpu == VMBUS_CONNECT_CPU && -	    vmbus_connection.conn_state == CONNECTED) +	if (cpu == VMBUS_CONNECT_CPU)  		return -EBUSY;  	/* @@ -277,9 +371,21 @@ int hv_synic_cleanup(unsigned int cpu)  	}  	mutex_unlock(&vmbus_connection.channel_mutex); -	if (channel_found && vmbus_connection.conn_state == CONNECTED) +	if (channel_found) +		return -EBUSY; + +	/* +	 * channel_found == false means that any channels that were previously +	 * assigned to the CPU have been reassigned elsewhere with a call of +	 * vmbus_send_modifychannel().  Scan the event flags page looking for +	 * bits that are set and waiting with a timeout for vmbus_chan_sched() +	 * to process such bits.  If bits are still set after this operation +	 * and VMBus is connected, fail the CPU offlining operation. +	 */ +	if (vmbus_proto_version >= VERSION_WIN10_V4_1 && hv_synic_event_pending())  		return -EBUSY; +always_cleanup:  	hv_stimer_legacy_cleanup(cpu);  	hv_synic_disable_regs(cpu); | 
