Commit af5a7e99 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull Hyper-V updates from Sasha Levin:

 - first round of vmbus hibernation support (Dexuan Cui)

 - remove dependencies on PAGE_SIZE (Maya Nakamura)

 - move the hyper-v tools/ code into the tools build system (Andy
   Shevchenko)

 - hyper-v balloon cleanups (Dexuan Cui)

* tag 'hyperv-next-signed' of git://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux:
  Drivers: hv: vmbus: Resume after fixing up old primary channels
  Drivers: hv: vmbus: Suspend after cleaning up hv_sock and sub channels
  Drivers: hv: vmbus: Clean up hv_sock channels by force upon suspend
  Drivers: hv: vmbus: Suspend/resume the vmbus itself for hibernation
  Drivers: hv: vmbus: Ignore the offers when resuming from hibernation
  Drivers: hv: vmbus: Implement suspend/resume for VSC drivers for hibernation
  Drivers: hv: vmbus: Add a helper function is_sub_channel()
  Drivers: hv: vmbus: Suspend/resume the synic for hibernation
  Drivers: hv: vmbus: Break out synic enable and disable operations
  HID: hv: Remove dependencies on PAGE_SIZE for ring buffer
  Tools: hv: move to tools buildsystem
  hv_balloon: Reorganize the probe function
  hv_balloon: Use a static page for the balloon_up send buffer
parents 0b36c9ee d8bd2d44
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -104,8 +104,8 @@ struct synthhid_input_report {

#pragma pack(pop)

#define INPUTVSC_SEND_RING_BUFFER_SIZE		(10*PAGE_SIZE)
#define INPUTVSC_RECV_RING_BUFFER_SIZE		(10*PAGE_SIZE)
#define INPUTVSC_SEND_RING_BUFFER_SIZE		(40 * 1024)
#define INPUTVSC_RECV_RING_BUFFER_SIZE		(40 * 1024)


enum pipe_prot_msg_type {
+144 −17
Original line number Diff line number Diff line
@@ -407,6 +407,14 @@ void hv_process_channel_removal(struct vmbus_channel *channel)
		cpumask_clear_cpu(channel->target_cpu,
				  &primary_channel->alloced_cpus_in_node);

	/*
	 * Upon suspend, an in-use hv_sock channel is marked as "rescinded" and
	 * the relid is invalidated; after hibernation, when the user-space app
	 * destroys the channel, the relid is INVALID_RELID, and in this case
	 * it's unnecessary and unsafe to release the old relid, since the same
	 * relid can refer to a completely different channel now.
	 */
	if (channel->offermsg.child_relid != INVALID_RELID)
		vmbus_release_relid(channel->offermsg.child_relid);

	free_channel(channel);
@@ -545,6 +553,10 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)

	mutex_lock(&vmbus_connection.channel_mutex);

	/* Remember the channels that should be cleaned up upon suspend. */
	if (is_hvsock_channel(newchannel) || is_sub_channel(newchannel))
		atomic_inc(&vmbus_connection.nr_chan_close_on_suspend);

	/*
	 * Now that we have acquired the channel_mutex,
	 * we can release the potentially racing rescind thread.
@@ -847,6 +859,67 @@ void vmbus_initiate_unload(bool crash)
		vmbus_wait_for_unload();
}

static void check_ready_for_resume_event(void)
{
	/*
	 * If all the old primary channels have been fixed up, then it's safe
	 * to resume.
	 */
	if (atomic_dec_and_test(&vmbus_connection.nr_chan_fixup_on_resume))
		complete(&vmbus_connection.ready_for_resume_event);
}

static void vmbus_setup_channel_state(struct vmbus_channel *channel,
				      struct vmbus_channel_offer_channel *offer)
{
	/*
	 * Setup state for signalling the host.
	 */
	channel->sig_event = VMBUS_EVENT_CONNECTION_ID;

	if (vmbus_proto_version != VERSION_WS2008) {
		channel->is_dedicated_interrupt =
				(offer->is_dedicated_interrupt != 0);
		channel->sig_event = offer->connection_id;
	}

	memcpy(&channel->offermsg, offer,
	       sizeof(struct vmbus_channel_offer_channel));
	channel->monitor_grp = (u8)offer->monitorid / 32;
	channel->monitor_bit = (u8)offer->monitorid % 32;
}

/*
 * find_primary_channel_by_offer - Get the channel object given the new offer.
 * This is only used in the resume path of hibernation.
 */
static struct vmbus_channel *
find_primary_channel_by_offer(const struct vmbus_channel_offer_channel *offer)
{
	struct vmbus_channel *channel = NULL, *iter;
	const guid_t *inst1, *inst2;

	/* Ignore sub-channel offers. */
	if (offer->offer.sub_channel_index != 0)
		return NULL;

	mutex_lock(&vmbus_connection.channel_mutex);

	list_for_each_entry(iter, &vmbus_connection.chn_list, listentry) {
		inst1 = &iter->offermsg.offer.if_instance;
		inst2 = &offer->offer.if_instance;

		if (guid_equal(inst1, inst2)) {
			channel = iter;
			break;
		}
	}

	mutex_unlock(&vmbus_connection.channel_mutex);

	return channel;
}

/*
 * vmbus_onoffer - Handler for channel offers from vmbus in parent partition.
 *
@@ -854,12 +927,58 @@ void vmbus_initiate_unload(bool crash)
static void vmbus_onoffer(struct vmbus_channel_message_header *hdr)
{
	struct vmbus_channel_offer_channel *offer;
	struct vmbus_channel *newchannel;
	struct vmbus_channel *oldchannel, *newchannel;
	size_t offer_sz;

	offer = (struct vmbus_channel_offer_channel *)hdr;

	trace_vmbus_onoffer(offer);

	oldchannel = find_primary_channel_by_offer(offer);

	if (oldchannel != NULL) {
		atomic_dec(&vmbus_connection.offer_in_progress);

		/*
		 * We're resuming from hibernation: all the sub-channel and
		 * hv_sock channels we had before the hibernation should have
		 * been cleaned up, and now we must be seeing a re-offered
		 * primary channel that we had before the hibernation.
		 */

		WARN_ON(oldchannel->offermsg.child_relid != INVALID_RELID);
		/* Fix up the relid. */
		oldchannel->offermsg.child_relid = offer->child_relid;

		offer_sz = sizeof(*offer);
		if (memcmp(offer, &oldchannel->offermsg, offer_sz) == 0) {
			check_ready_for_resume_event();
			return;
		}

		/*
		 * This is not an error, since the host can also change the
		 * other field(s) of the offer, e.g. on WS RS5 (Build 17763),
		 * the offer->connection_id of the Mellanox VF vmbus device
		 * can change when the host reoffers the device upon resume.
		 */
		pr_debug("vmbus offer changed: relid=%d\n",
			 offer->child_relid);

		print_hex_dump_debug("Old vmbus offer: ", DUMP_PREFIX_OFFSET,
				     16, 4, &oldchannel->offermsg, offer_sz,
				     false);
		print_hex_dump_debug("New vmbus offer: ", DUMP_PREFIX_OFFSET,
				     16, 4, offer, offer_sz, false);

		/* Fix up the old channel. */
		vmbus_setup_channel_state(oldchannel, offer);

		check_ready_for_resume_event();

		return;
	}

	/* Allocate the channel object and save this offer. */
	newchannel = alloc_channel();
	if (!newchannel) {
@@ -869,23 +988,19 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr)
		return;
	}

	/*
	 * Setup state for signalling the host.
	 */
	newchannel->sig_event = VMBUS_EVENT_CONNECTION_ID;
	vmbus_setup_channel_state(newchannel, offer);

	if (vmbus_proto_version != VERSION_WS2008) {
		newchannel->is_dedicated_interrupt =
				(offer->is_dedicated_interrupt != 0);
		newchannel->sig_event = offer->connection_id;
	vmbus_process_offer(newchannel);
}

	memcpy(&newchannel->offermsg, offer,
	       sizeof(struct vmbus_channel_offer_channel));
	newchannel->monitor_grp = (u8)offer->monitorid / 32;
	newchannel->monitor_bit = (u8)offer->monitorid % 32;

	vmbus_process_offer(newchannel);
static void check_ready_for_suspend_event(void)
{
	/*
	 * If all the sub-channels or hv_sock channels have been cleaned up,
	 * then it's safe to suspend.
	 */
	if (atomic_dec_and_test(&vmbus_connection.nr_chan_close_on_suspend))
		complete(&vmbus_connection.ready_for_suspend_event);
}

/*
@@ -898,6 +1013,7 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
	struct vmbus_channel_rescind_offer *rescind;
	struct vmbus_channel *channel;
	struct device *dev;
	bool clean_up_chan_for_suspend;

	rescind = (struct vmbus_channel_rescind_offer *)hdr;

@@ -937,6 +1053,8 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
		return;
	}

	clean_up_chan_for_suspend = is_hvsock_channel(channel) ||
				    is_sub_channel(channel);
	/*
	 * Before setting channel->rescind in vmbus_rescind_cleanup(), we
	 * should make sure the channel callback is not running any more.
@@ -962,6 +1080,10 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
	if (channel->device_obj) {
		if (channel->chn_rescind_callback) {
			channel->chn_rescind_callback(channel);

			if (clean_up_chan_for_suspend)
				check_ready_for_suspend_event();

			return;
		}
		/*
@@ -994,6 +1116,11 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
		}
		mutex_unlock(&vmbus_connection.channel_mutex);
	}

	/* The "channel" may have been freed. Do not access it any longer. */

	if (clean_up_chan_for_suspend)
		check_ready_for_suspend_event();
}

void vmbus_hvsock_device_unregister(struct vmbus_channel *channel)
+6 −2
Original line number Diff line number Diff line
@@ -26,6 +26,11 @@
struct vmbus_connection vmbus_connection = {
	.conn_state		= DISCONNECTED,
	.next_gpadl_handle	= ATOMIC_INIT(0xE1E10),

	.ready_for_suspend_event= COMPLETION_INITIALIZER(
				  vmbus_connection.ready_for_suspend_event),
	.ready_for_resume_event	= COMPLETION_INITIALIZER(
				  vmbus_connection.ready_for_resume_event),
};
EXPORT_SYMBOL_GPL(vmbus_connection);

@@ -59,8 +64,7 @@ static __u32 vmbus_get_next_version(__u32 current_version)
	}
}

static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo,
					__u32 version)
int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)
{
	int ret = 0;
	unsigned int cur_cpu;
+37 −29
Original line number Diff line number Diff line
@@ -154,7 +154,7 @@ void hv_synic_free(void)
 * retrieve the initialized message and event pages.  Otherwise, we create and
 * initialize the message and event pages.
 */
int hv_synic_init(unsigned int cpu)
void hv_synic_enable_regs(unsigned int cpu)
{
	struct hv_per_cpu_context *hv_cpu
		= per_cpu_ptr(hv_context.cpu_context, cpu);
@@ -196,6 +196,11 @@ int hv_synic_init(unsigned int cpu)
	sctrl.enable = 1;

	hv_set_synic_state(sctrl.as_uint64);
}

int hv_synic_init(unsigned int cpu)
{
	hv_synic_enable_regs(cpu);

	hv_stimer_init(cpu);

@@ -205,20 +210,45 @@ int hv_synic_init(unsigned int cpu)
/*
 * hv_synic_cleanup - Cleanup routine for hv_synic_init().
 */
int hv_synic_cleanup(unsigned int cpu)
void hv_synic_disable_regs(unsigned int cpu)
{
	union hv_synic_sint shared_sint;
	union hv_synic_simp simp;
	union hv_synic_siefp siefp;
	union hv_synic_scontrol sctrl;

	hv_get_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64);

	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_get_simp(simp.as_uint64);
	simp.simp_enabled = 0;
	simp.base_simp_gpa = 0;

	hv_set_simp(simp.as_uint64);

	hv_get_siefp(siefp.as_uint64);
	siefp.siefp_enabled = 0;
	siefp.base_siefp_gpa = 0;

	hv_set_siefp(siefp.as_uint64);

	/* Disable the global synic bit */
	hv_get_synic_state(sctrl.as_uint64);
	sctrl.enable = 0;
	hv_set_synic_state(sctrl.as_uint64);
}

int hv_synic_cleanup(unsigned int cpu)
{
	struct vmbus_channel *channel, *sc;
	bool channel_found = false;
	unsigned long flags;

	hv_get_synic_state(sctrl.as_uint64);
	if (sctrl.enable != 1)
		return -EFAULT;

	/*
	 * Search for channels which are bound to the CPU we're about to
	 * cleanup. In case we find one and vmbus is still connected we need to
@@ -249,29 +279,7 @@ int hv_synic_cleanup(unsigned int cpu)

	hv_stimer_cleanup(cpu);

	hv_get_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64);

	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_get_simp(simp.as_uint64);
	simp.simp_enabled = 0;
	simp.base_simp_gpa = 0;

	hv_set_simp(simp.as_uint64);

	hv_get_siefp(siefp.as_uint64);
	siefp.siefp_enabled = 0;
	siefp.base_siefp_gpa = 0;

	hv_set_siefp(siefp.as_uint64);

	/* Disable the global synic bit */
	sctrl.enable = 0;
	hv_set_synic_state(sctrl.as_uint64);
	hv_synic_disable_regs(cpu);

	return 0;
}
+70 −73
Original line number Diff line number Diff line
@@ -494,7 +494,7 @@ enum hv_dm_state {


static __u8 recv_buffer[PAGE_SIZE];
static __u8 *send_buffer;
static __u8 balloon_up_send_buffer[PAGE_SIZE];
#define PAGES_IN_2M	512
#define HA_CHUNK (32 * 1024)

@@ -1292,8 +1292,8 @@ static void balloon_up(struct work_struct *dummy)
	}

	while (!done) {
		bl_resp = (struct dm_balloon_response *)send_buffer;
		memset(send_buffer, 0, PAGE_SIZE);
		memset(balloon_up_send_buffer, 0, PAGE_SIZE);
		bl_resp = (struct dm_balloon_response *)balloon_up_send_buffer;
		bl_resp->hdr.type = DM_BALLOON_RESPONSE;
		bl_resp->hdr.size = sizeof(struct dm_balloon_response);
		bl_resp->more_pages = 1;
@@ -1564,58 +1564,18 @@ static void balloon_onchannelcallback(void *context)

}

static int balloon_probe(struct hv_device *dev,
			const struct hv_vmbus_device_id *dev_id)
static int balloon_connect_vsp(struct hv_device *dev)
{
	int ret;
	unsigned long t;
	struct dm_version_request version_req;
	struct dm_capabilities cap_msg;

#ifdef CONFIG_MEMORY_HOTPLUG
	do_hot_add = hot_add;
#else
	do_hot_add = false;
#endif

	/*
	 * First allocate a send buffer.
	 */

	send_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
	if (!send_buffer)
		return -ENOMEM;
	unsigned long t;
	int ret;

	ret = vmbus_open(dev->channel, dm_ring_size, dm_ring_size, NULL, 0,
			 balloon_onchannelcallback, dev);

	if (ret)
		goto probe_error0;

	dm_device.dev = dev;
	dm_device.state = DM_INITIALIZING;
	dm_device.next_version = DYNMEM_PROTOCOL_VERSION_WIN8;
	init_completion(&dm_device.host_event);
	init_completion(&dm_device.config_event);
	INIT_LIST_HEAD(&dm_device.ha_region_list);
	spin_lock_init(&dm_device.ha_lock);
	INIT_WORK(&dm_device.balloon_wrk.wrk, balloon_up);
	INIT_WORK(&dm_device.ha_wrk.wrk, hot_add_req);
	dm_device.host_specified_ha_region = false;

	dm_device.thread =
		 kthread_run(dm_thread_func, &dm_device, "hv_balloon");
	if (IS_ERR(dm_device.thread)) {
		ret = PTR_ERR(dm_device.thread);
		goto probe_error1;
	}

#ifdef CONFIG_MEMORY_HOTPLUG
	set_online_page_callback(&hv_online_page);
	register_memory_notifier(&hv_memory_nb);
#endif
		return ret;

	hv_set_drvdata(dev, &dm_device);
	/*
	 * Initiate the hand shake with the host and negotiate
	 * a version that the host can support. We start with the
@@ -1632,15 +1592,14 @@ static int balloon_probe(struct hv_device *dev,

	ret = vmbus_sendpacket(dev->channel, &version_req,
			       sizeof(struct dm_version_request),
				(unsigned long)NULL,
				VM_PKT_DATA_INBAND, 0);
			       (unsigned long)NULL, VM_PKT_DATA_INBAND, 0);
	if (ret)
		goto probe_error2;
		goto out;

	t = wait_for_completion_timeout(&dm_device.host_event, 5*HZ);
	if (t == 0) {
		ret = -ETIMEDOUT;
		goto probe_error2;
		goto out;
	}

	/*
@@ -1648,8 +1607,8 @@ static int balloon_probe(struct hv_device *dev,
	 * fail the probe function.
	 */
	if (dm_device.state == DM_INIT_ERROR) {
		ret = -ETIMEDOUT;
		goto probe_error2;
		ret = -EPROTO;
		goto out;
	}

	pr_info("Using Dynamic Memory protocol version %u.%u\n",
@@ -1683,15 +1642,14 @@ static int balloon_probe(struct hv_device *dev,

	ret = vmbus_sendpacket(dev->channel, &cap_msg,
			       sizeof(struct dm_capabilities),
				(unsigned long)NULL,
				VM_PKT_DATA_INBAND, 0);
			       (unsigned long)NULL, VM_PKT_DATA_INBAND, 0);
	if (ret)
		goto probe_error2;
		goto out;

	t = wait_for_completion_timeout(&dm_device.host_event, 5*HZ);
	if (t == 0) {
		ret = -ETIMEDOUT;
		goto probe_error2;
		goto out;
	}

	/*
@@ -1699,25 +1657,65 @@ static int balloon_probe(struct hv_device *dev,
	 * fail the probe function.
	 */
	if (dm_device.state == DM_INIT_ERROR) {
		ret = -ETIMEDOUT;
		goto probe_error2;
		ret = -EPROTO;
		goto out;
	}

	return 0;
out:
	vmbus_close(dev->channel);
	return ret;
}

static int balloon_probe(struct hv_device *dev,
			 const struct hv_vmbus_device_id *dev_id)
{
	int ret;

#ifdef CONFIG_MEMORY_HOTPLUG
	do_hot_add = hot_add;
#else
	do_hot_add = false;
#endif
	dm_device.dev = dev;
	dm_device.state = DM_INITIALIZING;
	dm_device.next_version = DYNMEM_PROTOCOL_VERSION_WIN8;
	init_completion(&dm_device.host_event);
	init_completion(&dm_device.config_event);
	INIT_LIST_HEAD(&dm_device.ha_region_list);
	spin_lock_init(&dm_device.ha_lock);
	INIT_WORK(&dm_device.balloon_wrk.wrk, balloon_up);
	INIT_WORK(&dm_device.ha_wrk.wrk, hot_add_req);
	dm_device.host_specified_ha_region = false;

#ifdef CONFIG_MEMORY_HOTPLUG
	set_online_page_callback(&hv_online_page);
	register_memory_notifier(&hv_memory_nb);
#endif

	hv_set_drvdata(dev, &dm_device);

	ret = balloon_connect_vsp(dev);
	if (ret != 0)
		return ret;

	dm_device.state = DM_INITIALIZED;
	last_post_time = jiffies;

	dm_device.thread =
		 kthread_run(dm_thread_func, &dm_device, "hv_balloon");
	if (IS_ERR(dm_device.thread)) {
		ret = PTR_ERR(dm_device.thread);
		goto probe_error;
	}

	return 0;

probe_error2:
probe_error:
	vmbus_close(dev->channel);
#ifdef CONFIG_MEMORY_HOTPLUG
	unregister_memory_notifier(&hv_memory_nb);
	restore_online_page_callback(&hv_online_page);
#endif
	kthread_stop(dm_device.thread);

probe_error1:
	vmbus_close(dev->channel);
probe_error0:
	kfree(send_buffer);
	return ret;
}

@@ -1734,12 +1732,11 @@ static int balloon_remove(struct hv_device *dev)
	cancel_work_sync(&dm->balloon_wrk.wrk);
	cancel_work_sync(&dm->ha_wrk.wrk);

	vmbus_close(dev->channel);
	kthread_stop(dm->thread);
	kfree(send_buffer);
	vmbus_close(dev->channel);
#ifdef CONFIG_MEMORY_HOTPLUG
	restore_online_page_callback(&hv_online_page);
	unregister_memory_notifier(&hv_memory_nb);
	restore_online_page_callback(&hv_online_page);
#endif
	spin_lock_irqsave(&dm_device.ha_lock, flags);
	list_for_each_entry_safe(has, tmp, &dm->ha_region_list, list) {
Loading