18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (c) 2009, Microsoft Corporation.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Authors:
78c2ecf20Sopenharmony_ci *   Haiyang Zhang <haiyangz@microsoft.com>
88c2ecf20Sopenharmony_ci *   Hank Janssen  <hjanssen@microsoft.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/sched.h>
148c2ecf20Sopenharmony_ci#include <linux/wait.h>
158c2ecf20Sopenharmony_ci#include <linux/delay.h>
168c2ecf20Sopenharmony_ci#include <linux/mm.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
208c2ecf20Sopenharmony_ci#include <linux/hyperv.h>
218c2ecf20Sopenharmony_ci#include <linux/export.h>
228c2ecf20Sopenharmony_ci#include <asm/mshyperv.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#include "hyperv_vmbus.h"
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistruct vmbus_connection vmbus_connection = {
288c2ecf20Sopenharmony_ci	.conn_state		= DISCONNECTED,
298c2ecf20Sopenharmony_ci	.next_gpadl_handle	= ATOMIC_INIT(0xE1E10),
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	.ready_for_suspend_event= COMPLETION_INITIALIZER(
328c2ecf20Sopenharmony_ci				  vmbus_connection.ready_for_suspend_event),
338c2ecf20Sopenharmony_ci	.ready_for_resume_event	= COMPLETION_INITIALIZER(
348c2ecf20Sopenharmony_ci				  vmbus_connection.ready_for_resume_event),
358c2ecf20Sopenharmony_ci};
368c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(vmbus_connection);
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/*
398c2ecf20Sopenharmony_ci * Negotiated protocol version with the host.
408c2ecf20Sopenharmony_ci */
418c2ecf20Sopenharmony_ci__u32 vmbus_proto_version;
428c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(vmbus_proto_version);
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci/*
458c2ecf20Sopenharmony_ci * Table of VMBus versions listed from newest to oldest.
468c2ecf20Sopenharmony_ci */
478c2ecf20Sopenharmony_cistatic __u32 vmbus_versions[] = {
488c2ecf20Sopenharmony_ci	VERSION_WIN10_V5_2,
498c2ecf20Sopenharmony_ci	VERSION_WIN10_V5_1,
508c2ecf20Sopenharmony_ci	VERSION_WIN10_V5,
518c2ecf20Sopenharmony_ci	VERSION_WIN10_V4_1,
528c2ecf20Sopenharmony_ci	VERSION_WIN10,
538c2ecf20Sopenharmony_ci	VERSION_WIN8_1,
548c2ecf20Sopenharmony_ci	VERSION_WIN8,
558c2ecf20Sopenharmony_ci	VERSION_WIN7,
568c2ecf20Sopenharmony_ci	VERSION_WS2008
578c2ecf20Sopenharmony_ci};
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci/*
608c2ecf20Sopenharmony_ci * Maximal VMBus protocol version guests can negotiate.  Useful to cap the
618c2ecf20Sopenharmony_ci * VMBus version for testing and debugging purpose.
628c2ecf20Sopenharmony_ci */
638c2ecf20Sopenharmony_cistatic uint max_version = VERSION_WIN10_V5_2;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cimodule_param(max_version, uint, S_IRUGO);
668c2ecf20Sopenharmony_ciMODULE_PARM_DESC(max_version,
678c2ecf20Sopenharmony_ci		 "Maximal VMBus protocol version which can be negotiated");
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ciint vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	int ret = 0;
728c2ecf20Sopenharmony_ci	struct vmbus_channel_initiate_contact *msg;
738c2ecf20Sopenharmony_ci	unsigned long flags;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	init_completion(&msginfo->waitevent);
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	msg = (struct vmbus_channel_initiate_contact *)msginfo->msg;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	memset(msg, 0, sizeof(*msg));
808c2ecf20Sopenharmony_ci	msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT;
818c2ecf20Sopenharmony_ci	msg->vmbus_version_requested = version;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	/*
848c2ecf20Sopenharmony_ci	 * VMBus protocol 5.0 (VERSION_WIN10_V5) and higher require that we must
858c2ecf20Sopenharmony_ci	 * use VMBUS_MESSAGE_CONNECTION_ID_4 for the Initiate Contact Message,
868c2ecf20Sopenharmony_ci	 * and for subsequent messages, we must use the Message Connection ID
878c2ecf20Sopenharmony_ci	 * field in the host-returned Version Response Message. And, with
888c2ecf20Sopenharmony_ci	 * VERSION_WIN10_V5 and higher, we don't use msg->interrupt_page, but we
898c2ecf20Sopenharmony_ci	 * tell the host explicitly that we still use VMBUS_MESSAGE_SINT(2) for
908c2ecf20Sopenharmony_ci	 * compatibility.
918c2ecf20Sopenharmony_ci	 *
928c2ecf20Sopenharmony_ci	 * On old hosts, we should always use VMBUS_MESSAGE_CONNECTION_ID (1).
938c2ecf20Sopenharmony_ci	 */
948c2ecf20Sopenharmony_ci	if (version >= VERSION_WIN10_V5) {
958c2ecf20Sopenharmony_ci		msg->msg_sint = VMBUS_MESSAGE_SINT;
968c2ecf20Sopenharmony_ci		vmbus_connection.msg_conn_id = VMBUS_MESSAGE_CONNECTION_ID_4;
978c2ecf20Sopenharmony_ci	} else {
988c2ecf20Sopenharmony_ci		msg->interrupt_page = virt_to_phys(vmbus_connection.int_page);
998c2ecf20Sopenharmony_ci		vmbus_connection.msg_conn_id = VMBUS_MESSAGE_CONNECTION_ID;
1008c2ecf20Sopenharmony_ci	}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages[0]);
1038c2ecf20Sopenharmony_ci	msg->monitor_page2 = virt_to_phys(vmbus_connection.monitor_pages[1]);
1048c2ecf20Sopenharmony_ci	msg->target_vcpu = hv_cpu_number_to_vp_number(VMBUS_CONNECT_CPU);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	/*
1078c2ecf20Sopenharmony_ci	 * Add to list before we send the request since we may
1088c2ecf20Sopenharmony_ci	 * receive the response before returning from this routine
1098c2ecf20Sopenharmony_ci	 */
1108c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
1118c2ecf20Sopenharmony_ci	list_add_tail(&msginfo->msglistentry,
1128c2ecf20Sopenharmony_ci		      &vmbus_connection.chn_msg_list);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	ret = vmbus_post_msg(msg,
1178c2ecf20Sopenharmony_ci			     sizeof(struct vmbus_channel_initiate_contact),
1188c2ecf20Sopenharmony_ci			     true);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	trace_vmbus_negotiate_version(msg, ret);
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	if (ret != 0) {
1238c2ecf20Sopenharmony_ci		spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
1248c2ecf20Sopenharmony_ci		list_del(&msginfo->msglistentry);
1258c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
1268c2ecf20Sopenharmony_ci					flags);
1278c2ecf20Sopenharmony_ci		return ret;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	/* Wait for the connection response */
1318c2ecf20Sopenharmony_ci	wait_for_completion(&msginfo->waitevent);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
1348c2ecf20Sopenharmony_ci	list_del(&msginfo->msglistentry);
1358c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/* Check if successful */
1388c2ecf20Sopenharmony_ci	if (msginfo->response.version_response.version_supported) {
1398c2ecf20Sopenharmony_ci		vmbus_connection.conn_state = CONNECTED;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		if (version >= VERSION_WIN10_V5)
1428c2ecf20Sopenharmony_ci			vmbus_connection.msg_conn_id =
1438c2ecf20Sopenharmony_ci				msginfo->response.version_response.msg_conn_id;
1448c2ecf20Sopenharmony_ci	} else {
1458c2ecf20Sopenharmony_ci		return -ECONNREFUSED;
1468c2ecf20Sopenharmony_ci	}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	return ret;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci/*
1528c2ecf20Sopenharmony_ci * vmbus_connect - Sends a connect request on the partition service connection
1538c2ecf20Sopenharmony_ci */
1548c2ecf20Sopenharmony_ciint vmbus_connect(void)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	struct vmbus_channel_msginfo *msginfo = NULL;
1578c2ecf20Sopenharmony_ci	int i, ret = 0;
1588c2ecf20Sopenharmony_ci	__u32 version;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	/* Initialize the vmbus connection */
1618c2ecf20Sopenharmony_ci	vmbus_connection.conn_state = CONNECTING;
1628c2ecf20Sopenharmony_ci	vmbus_connection.work_queue = create_workqueue("hv_vmbus_con");
1638c2ecf20Sopenharmony_ci	if (!vmbus_connection.work_queue) {
1648c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1658c2ecf20Sopenharmony_ci		goto cleanup;
1668c2ecf20Sopenharmony_ci	}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	vmbus_connection.handle_primary_chan_wq =
1698c2ecf20Sopenharmony_ci		create_workqueue("hv_pri_chan");
1708c2ecf20Sopenharmony_ci	if (!vmbus_connection.handle_primary_chan_wq) {
1718c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1728c2ecf20Sopenharmony_ci		goto cleanup;
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	vmbus_connection.handle_sub_chan_wq =
1768c2ecf20Sopenharmony_ci		create_workqueue("hv_sub_chan");
1778c2ecf20Sopenharmony_ci	if (!vmbus_connection.handle_sub_chan_wq) {
1788c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1798c2ecf20Sopenharmony_ci		goto cleanup;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&vmbus_connection.chn_msg_list);
1838c2ecf20Sopenharmony_ci	spin_lock_init(&vmbus_connection.channelmsg_lock);
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&vmbus_connection.chn_list);
1868c2ecf20Sopenharmony_ci	mutex_init(&vmbus_connection.channel_mutex);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	/*
1898c2ecf20Sopenharmony_ci	 * Setup the vmbus event connection for channel interrupt
1908c2ecf20Sopenharmony_ci	 * abstraction stuff
1918c2ecf20Sopenharmony_ci	 */
1928c2ecf20Sopenharmony_ci	vmbus_connection.int_page =
1938c2ecf20Sopenharmony_ci	(void *)hv_alloc_hyperv_zeroed_page();
1948c2ecf20Sopenharmony_ci	if (vmbus_connection.int_page == NULL) {
1958c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1968c2ecf20Sopenharmony_ci		goto cleanup;
1978c2ecf20Sopenharmony_ci	}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	vmbus_connection.recv_int_page = vmbus_connection.int_page;
2008c2ecf20Sopenharmony_ci	vmbus_connection.send_int_page =
2018c2ecf20Sopenharmony_ci		(void *)((unsigned long)vmbus_connection.int_page +
2028c2ecf20Sopenharmony_ci			(HV_HYP_PAGE_SIZE >> 1));
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	/*
2058c2ecf20Sopenharmony_ci	 * Setup the monitor notification facility. The 1st page for
2068c2ecf20Sopenharmony_ci	 * parent->child and the 2nd page for child->parent
2078c2ecf20Sopenharmony_ci	 */
2088c2ecf20Sopenharmony_ci	vmbus_connection.monitor_pages[0] = (void *)hv_alloc_hyperv_zeroed_page();
2098c2ecf20Sopenharmony_ci	vmbus_connection.monitor_pages[1] = (void *)hv_alloc_hyperv_zeroed_page();
2108c2ecf20Sopenharmony_ci	if ((vmbus_connection.monitor_pages[0] == NULL) ||
2118c2ecf20Sopenharmony_ci	    (vmbus_connection.monitor_pages[1] == NULL)) {
2128c2ecf20Sopenharmony_ci		ret = -ENOMEM;
2138c2ecf20Sopenharmony_ci		goto cleanup;
2148c2ecf20Sopenharmony_ci	}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	msginfo = kzalloc(sizeof(*msginfo) +
2178c2ecf20Sopenharmony_ci			  sizeof(struct vmbus_channel_initiate_contact),
2188c2ecf20Sopenharmony_ci			  GFP_KERNEL);
2198c2ecf20Sopenharmony_ci	if (msginfo == NULL) {
2208c2ecf20Sopenharmony_ci		ret = -ENOMEM;
2218c2ecf20Sopenharmony_ci		goto cleanup;
2228c2ecf20Sopenharmony_ci	}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	/*
2258c2ecf20Sopenharmony_ci	 * Negotiate a compatible VMBUS version number with the
2268c2ecf20Sopenharmony_ci	 * host. We start with the highest number we can support
2278c2ecf20Sopenharmony_ci	 * and work our way down until we negotiate a compatible
2288c2ecf20Sopenharmony_ci	 * version.
2298c2ecf20Sopenharmony_ci	 */
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	for (i = 0; ; i++) {
2328c2ecf20Sopenharmony_ci		if (i == ARRAY_SIZE(vmbus_versions)) {
2338c2ecf20Sopenharmony_ci			ret = -EDOM;
2348c2ecf20Sopenharmony_ci			goto cleanup;
2358c2ecf20Sopenharmony_ci		}
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci		version = vmbus_versions[i];
2388c2ecf20Sopenharmony_ci		if (version > max_version)
2398c2ecf20Sopenharmony_ci			continue;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci		ret = vmbus_negotiate_version(msginfo, version);
2428c2ecf20Sopenharmony_ci		if (ret == -ETIMEDOUT)
2438c2ecf20Sopenharmony_ci			goto cleanup;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci		if (vmbus_connection.conn_state == CONNECTED)
2468c2ecf20Sopenharmony_ci			break;
2478c2ecf20Sopenharmony_ci	}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	vmbus_proto_version = version;
2508c2ecf20Sopenharmony_ci	pr_info("Vmbus version:%d.%d\n",
2518c2ecf20Sopenharmony_ci		version >> 16, version & 0xFFFF);
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	vmbus_connection.channels = kcalloc(MAX_CHANNEL_RELIDS,
2548c2ecf20Sopenharmony_ci					    sizeof(struct vmbus_channel *),
2558c2ecf20Sopenharmony_ci					    GFP_KERNEL);
2568c2ecf20Sopenharmony_ci	if (vmbus_connection.channels == NULL) {
2578c2ecf20Sopenharmony_ci		ret = -ENOMEM;
2588c2ecf20Sopenharmony_ci		goto cleanup;
2598c2ecf20Sopenharmony_ci	}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	kfree(msginfo);
2628c2ecf20Sopenharmony_ci	return 0;
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_cicleanup:
2658c2ecf20Sopenharmony_ci	pr_err("Unable to connect to host\n");
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	vmbus_connection.conn_state = DISCONNECTED;
2688c2ecf20Sopenharmony_ci	vmbus_disconnect();
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	kfree(msginfo);
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	return ret;
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_civoid vmbus_disconnect(void)
2768c2ecf20Sopenharmony_ci{
2778c2ecf20Sopenharmony_ci	/*
2788c2ecf20Sopenharmony_ci	 * First send the unload request to the host.
2798c2ecf20Sopenharmony_ci	 */
2808c2ecf20Sopenharmony_ci	vmbus_initiate_unload(false);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	if (vmbus_connection.handle_sub_chan_wq)
2838c2ecf20Sopenharmony_ci		destroy_workqueue(vmbus_connection.handle_sub_chan_wq);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	if (vmbus_connection.handle_primary_chan_wq)
2868c2ecf20Sopenharmony_ci		destroy_workqueue(vmbus_connection.handle_primary_chan_wq);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	if (vmbus_connection.work_queue)
2898c2ecf20Sopenharmony_ci		destroy_workqueue(vmbus_connection.work_queue);
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	if (vmbus_connection.int_page) {
2928c2ecf20Sopenharmony_ci		hv_free_hyperv_page((unsigned long)vmbus_connection.int_page);
2938c2ecf20Sopenharmony_ci		vmbus_connection.int_page = NULL;
2948c2ecf20Sopenharmony_ci	}
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	hv_free_hyperv_page((unsigned long)vmbus_connection.monitor_pages[0]);
2978c2ecf20Sopenharmony_ci	hv_free_hyperv_page((unsigned long)vmbus_connection.monitor_pages[1]);
2988c2ecf20Sopenharmony_ci	vmbus_connection.monitor_pages[0] = NULL;
2998c2ecf20Sopenharmony_ci	vmbus_connection.monitor_pages[1] = NULL;
3008c2ecf20Sopenharmony_ci}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci/*
3038c2ecf20Sopenharmony_ci * relid2channel - Get the channel object given its
3048c2ecf20Sopenharmony_ci * child relative id (ie channel id)
3058c2ecf20Sopenharmony_ci */
3068c2ecf20Sopenharmony_cistruct vmbus_channel *relid2channel(u32 relid)
3078c2ecf20Sopenharmony_ci{
3088c2ecf20Sopenharmony_ci	if (vmbus_connection.channels == NULL) {
3098c2ecf20Sopenharmony_ci		pr_warn_once("relid2channel: relid=%d: No channels mapped!\n", relid);
3108c2ecf20Sopenharmony_ci		return NULL;
3118c2ecf20Sopenharmony_ci	}
3128c2ecf20Sopenharmony_ci	if (WARN_ON(relid >= MAX_CHANNEL_RELIDS))
3138c2ecf20Sopenharmony_ci		return NULL;
3148c2ecf20Sopenharmony_ci	return READ_ONCE(vmbus_connection.channels[relid]);
3158c2ecf20Sopenharmony_ci}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci/*
3188c2ecf20Sopenharmony_ci * vmbus_on_event - Process a channel event notification
3198c2ecf20Sopenharmony_ci *
3208c2ecf20Sopenharmony_ci * For batched channels (default) optimize host to guest signaling
3218c2ecf20Sopenharmony_ci * by ensuring:
3228c2ecf20Sopenharmony_ci * 1. While reading the channel, we disable interrupts from host.
3238c2ecf20Sopenharmony_ci * 2. Ensure that we process all posted messages from the host
3248c2ecf20Sopenharmony_ci *    before returning from this callback.
3258c2ecf20Sopenharmony_ci * 3. Once we return, enable signaling from the host. Once this
3268c2ecf20Sopenharmony_ci *    state is set we check to see if additional packets are
3278c2ecf20Sopenharmony_ci *    available to read. In this case we repeat the process.
3288c2ecf20Sopenharmony_ci *    If this tasklet has been running for a long time
3298c2ecf20Sopenharmony_ci *    then reschedule ourselves.
3308c2ecf20Sopenharmony_ci */
3318c2ecf20Sopenharmony_civoid vmbus_on_event(unsigned long data)
3328c2ecf20Sopenharmony_ci{
3338c2ecf20Sopenharmony_ci	struct vmbus_channel *channel = (void *) data;
3348c2ecf20Sopenharmony_ci	unsigned long time_limit = jiffies + 2;
3358c2ecf20Sopenharmony_ci
3368c2ecf20Sopenharmony_ci	trace_vmbus_on_event(channel);
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	hv_debug_delay_test(channel, INTERRUPT_DELAY);
3398c2ecf20Sopenharmony_ci	do {
3408c2ecf20Sopenharmony_ci		void (*callback_fn)(void *);
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci		/* A channel once created is persistent even when
3438c2ecf20Sopenharmony_ci		 * there is no driver handling the device. An
3448c2ecf20Sopenharmony_ci		 * unloading driver sets the onchannel_callback to NULL.
3458c2ecf20Sopenharmony_ci		 */
3468c2ecf20Sopenharmony_ci		callback_fn = READ_ONCE(channel->onchannel_callback);
3478c2ecf20Sopenharmony_ci		if (unlikely(callback_fn == NULL))
3488c2ecf20Sopenharmony_ci			return;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci		(*callback_fn)(channel->channel_callback_context);
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci		if (channel->callback_mode != HV_CALL_BATCHED)
3538c2ecf20Sopenharmony_ci			return;
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci		if (likely(hv_end_read(&channel->inbound) == 0))
3568c2ecf20Sopenharmony_ci			return;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci		hv_begin_read(&channel->inbound);
3598c2ecf20Sopenharmony_ci	} while (likely(time_before(jiffies, time_limit)));
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	/* The time limit (2 jiffies) has been reached */
3628c2ecf20Sopenharmony_ci	tasklet_schedule(&channel->callback_event);
3638c2ecf20Sopenharmony_ci}
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci/*
3668c2ecf20Sopenharmony_ci * vmbus_post_msg - Send a msg on the vmbus's message connection
3678c2ecf20Sopenharmony_ci */
3688c2ecf20Sopenharmony_ciint vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep)
3698c2ecf20Sopenharmony_ci{
3708c2ecf20Sopenharmony_ci	struct vmbus_channel_message_header *hdr;
3718c2ecf20Sopenharmony_ci	union hv_connection_id conn_id;
3728c2ecf20Sopenharmony_ci	int ret = 0;
3738c2ecf20Sopenharmony_ci	int retries = 0;
3748c2ecf20Sopenharmony_ci	u32 usec = 1;
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci	conn_id.asu32 = 0;
3778c2ecf20Sopenharmony_ci	conn_id.u.id = vmbus_connection.msg_conn_id;
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	/*
3808c2ecf20Sopenharmony_ci	 * hv_post_message() can have transient failures because of
3818c2ecf20Sopenharmony_ci	 * insufficient resources. Retry the operation a couple of
3828c2ecf20Sopenharmony_ci	 * times before giving up.
3838c2ecf20Sopenharmony_ci	 */
3848c2ecf20Sopenharmony_ci	while (retries < 100) {
3858c2ecf20Sopenharmony_ci		ret = hv_post_message(conn_id, 1, buffer, buflen);
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci		switch (ret) {
3888c2ecf20Sopenharmony_ci		case HV_STATUS_INVALID_CONNECTION_ID:
3898c2ecf20Sopenharmony_ci			/*
3908c2ecf20Sopenharmony_ci			 * See vmbus_negotiate_version(): VMBus protocol 5.0
3918c2ecf20Sopenharmony_ci			 * and higher require that we must use
3928c2ecf20Sopenharmony_ci			 * VMBUS_MESSAGE_CONNECTION_ID_4 for the Initiate
3938c2ecf20Sopenharmony_ci			 * Contact message, but on old hosts that only
3948c2ecf20Sopenharmony_ci			 * support VMBus protocol 4.0 or lower, here we get
3958c2ecf20Sopenharmony_ci			 * HV_STATUS_INVALID_CONNECTION_ID and we should
3968c2ecf20Sopenharmony_ci			 * return an error immediately without retrying.
3978c2ecf20Sopenharmony_ci			 */
3988c2ecf20Sopenharmony_ci			hdr = buffer;
3998c2ecf20Sopenharmony_ci			if (hdr->msgtype == CHANNELMSG_INITIATE_CONTACT)
4008c2ecf20Sopenharmony_ci				return -EINVAL;
4018c2ecf20Sopenharmony_ci			/*
4028c2ecf20Sopenharmony_ci			 * We could get this if we send messages too
4038c2ecf20Sopenharmony_ci			 * frequently.
4048c2ecf20Sopenharmony_ci			 */
4058c2ecf20Sopenharmony_ci			ret = -EAGAIN;
4068c2ecf20Sopenharmony_ci			break;
4078c2ecf20Sopenharmony_ci		case HV_STATUS_INSUFFICIENT_MEMORY:
4088c2ecf20Sopenharmony_ci		case HV_STATUS_INSUFFICIENT_BUFFERS:
4098c2ecf20Sopenharmony_ci			ret = -ENOBUFS;
4108c2ecf20Sopenharmony_ci			break;
4118c2ecf20Sopenharmony_ci		case HV_STATUS_SUCCESS:
4128c2ecf20Sopenharmony_ci			return ret;
4138c2ecf20Sopenharmony_ci		default:
4148c2ecf20Sopenharmony_ci			pr_err("hv_post_msg() failed; error code:%d\n", ret);
4158c2ecf20Sopenharmony_ci			return -EINVAL;
4168c2ecf20Sopenharmony_ci		}
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci		retries++;
4198c2ecf20Sopenharmony_ci		if (can_sleep && usec > 1000)
4208c2ecf20Sopenharmony_ci			msleep(usec / 1000);
4218c2ecf20Sopenharmony_ci		else if (usec < MAX_UDELAY_MS * 1000)
4228c2ecf20Sopenharmony_ci			udelay(usec);
4238c2ecf20Sopenharmony_ci		else
4248c2ecf20Sopenharmony_ci			mdelay(usec / 1000);
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_ci		if (retries < 22)
4278c2ecf20Sopenharmony_ci			usec *= 2;
4288c2ecf20Sopenharmony_ci	}
4298c2ecf20Sopenharmony_ci	return ret;
4308c2ecf20Sopenharmony_ci}
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci/*
4338c2ecf20Sopenharmony_ci * vmbus_set_event - Send an event notification to the parent
4348c2ecf20Sopenharmony_ci */
4358c2ecf20Sopenharmony_civoid vmbus_set_event(struct vmbus_channel *channel)
4368c2ecf20Sopenharmony_ci{
4378c2ecf20Sopenharmony_ci	u32 child_relid = channel->offermsg.child_relid;
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	if (!channel->is_dedicated_interrupt)
4408c2ecf20Sopenharmony_ci		vmbus_send_interrupt(child_relid);
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	++channel->sig_events;
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	hv_do_fast_hypercall8(HVCALL_SIGNAL_EVENT, channel->sig_event);
4458c2ecf20Sopenharmony_ci}
4468c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(vmbus_set_event);
447