diff mbox

: An implementation of HyperV KVP functionality

Message ID 4CDFBFEC.4080209@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Dor Laor Nov. 14, 2010, 10:54 a.m. UTC
None
diff mbox

Patch

Index: linux.trees.git/drivers/staging/hv/kvp.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux.trees.git/drivers/staging/hv/kvp.c	2010-11-11 13:45:17.000000000 -0500
@@ -0,0 +1,404 @@ 
+/*
+ * An implementation of key value pair (KVP) functionality for Linux.
+ *
+ *
+ * Copyright (C) 2010, Novell, Inc.
+ * Author : K. Y. Srinivasan <ksrinivasan@novell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <linux/net.h>
+#include <linux/nls.h>
+#include <linux/connector.h>
+
+#include "logging.h"
+#include "osd.h"
+#include "vmbus.h"
+#include "vmbus_packet_format.h"
+#include "vmbus_channel_interface.h"
+#include "version_info.h"
+#include "channel.h"
+#include "vmbus_private.h"
+#include "vmbus_api.h"
+#include "utils.h"
+#include "kvp.h"
+
+
+/*
+ *
+ * The following definitions are shared with the user-mode component; do not
+ * change any of this without making the corresponding changes in
+ * the KVP user-mode component.
+ */
+
+#define CN_KVP_VAL             0x1 /* This supports queries from the kernel */
+#define CN_KVP_USER_VAL       0x2 /* This supports queries from the user */
+
+
+/*
+ * KVP protocol: The user mode component first registers with the
+ * the kernel component. Subsequently, the kernel component requests, data
+ * for the specified keys. In response to this message the user mode component
+ * fills in the value corresponding to the specified key. We overload the
+ * sequence field in the cn_msg header to define our KVP message types.
+ *
+ * XXXKYS: Have a shared header file between the user and kernel (TODO)
+ */
+
+enum kvp_op {
+	KVP_REGISTER = 0, /* Register the user mode component */
+	KVP_KERNEL_GET,/*Kernel is requesting the value for the specified key*/
+	KVP_KERNEL_SET, /*Kernel is providing the value for the specified key*/
+	KVP_USER_GET, /*User is requesting the value for the specified key*/
+	KVP_USER_SET /*User is providing the value for the specified key*/
+};
+
+
+
+#define KVP_KEY_SIZE    512
+#define KVP_VALUE_SIZE  2048
+
+
+typedef struct kvp_msg {
+	__u32 kvp_key; /* Key */
+	__u8  kvp_value[0]; /* Corresponding value */
+} kvp_msg_t;
+
+/*
+ * End of shared definitions.
+ */
+
+/*
+ * Registry value types.
+ */
+
+#define REG_SZ 1
+
+/*
+ * Array of keys we support in Linux.
+ *
+ */
+#define KVP_MAX_KEY	10
+#define KVP_LIC_VERSION 1
+
+
+static char *kvp_keys[KVP_MAX_KEY] = {"FullyQualifiedDomainName",
+				"IntegrationServicesVersion",
+				"NetworkAddressIPv4",
+				"NetworkAddressIPv6",
+				"OSBuildNumber",
+				"OSName",
+				"OSMajorVersion",
+				"OSMinorVersion",
+				"OSVersion",
+				"ProcessorArchitecture",
+				};
+
+/*
+ * Global state maintained for transaction that is being processed.
+ * Note that only one transaction can be active at any point in time.
+ *
+ * This state is set when we receive a request from the host; we
+ * cleanup this state when the transaction is completed - when we respond
+ * to the host with the key value.
+ */
+
+static u8 *recv_buffer; /* the receive buffer that we allocated */
+static int recv_len; /* number of bytes received. */
+static struct vmbus_channel *recv_channel; /*chn on which we got the request*/
+static u64 recv_req_id; /* request ID. */
+static int kvp_current_index;
+
+static int
+kvp_send_key(int index);
+
+static
+void kvp_respond_to_host(int key, char *value);
+
+static struct cb_id kvp_id = { CN_KVP_IDX, CN_KVP_VAL };
+static char kvp_name[] = "kvp_kernel_module";
+
+static bool kvp_transaction_active;
+
+static struct timer_list kvp_timer;
+
+static void kvp_timer_func(unsigned long __data)
+{
+	u32	key = *((u32 *)__data);
+	/*
+	 * If the timer fires, the user-mode component has not responded;
+	 * process the pending transaction.
+	 */
+	kvp_respond_to_host(key, "Guest timed out");
+}
+
+/*
+ * Callback when data is received from user mode.
+ */
+
+static void
+kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
+{
+	struct kvp_msg *message;
+
+	message = (struct kvp_msg *)msg->data;
+	if (msg->seq == KVP_REGISTER)
+		printk(KERN_WARNING "KVP: user-mode registering done.\n");
+
+	if (msg->seq == KVP_USER_SET) {
+		/*
+		 * Complete the transaction by forwarding the key value
+		 * to the host. But first, cancel the timeout.
+		 */
+		if (del_timer_sync(&kvp_timer))
+			kvp_respond_to_host(message->kvp_key,
+						message->kvp_value);
+	}
+}
+
+static int
+kvp_send_key(int index)
+{
+	struct cn_msg *msg;
+
+	msg = kzalloc(sizeof(*msg) + sizeof(kvp_msg_t) , GFP_ATOMIC);
+
+	if (msg) {
+		msg->id.idx =  CN_KVP_IDX;
+		msg->id.val = CN_KVP_VAL;
+		msg->seq = KVP_KERNEL_GET;
+		((kvp_msg_t *)msg->data)->kvp_key = index;
+		msg->len = sizeof(kvp_msg_t);
+		cn_netlink_send(msg, 0, GFP_ATOMIC);
+		kfree(msg);
+		return 0;
+	}
+	return 1;
+}
+
+/*
+ * Send a response back to the host.
+ * key specifies the key for which the value is being returned.
+ */
+
+static
+void kvp_respond_to_host(int key, char *value)
+{
+	ic_kvp_msg_t *kvp_msg;
+	ic_kvp_msg_enumerate_t *kvp_data;
+	char	*key_name;
+	struct icmsg_hdr *icmsghdrp;
+	int	keylen, valuelen;
+	u8	*buf;
+	u32	buf_len;
+	struct vmbus_channel *channel;
+	u64	req_id;
+
+	/*
+	 * If a transaction is not active; log and return.
+	 */
+
+	if (!kvp_transaction_active) {
+		/*
+		 * This is a spurious call!
+		 */
+		printk(KERN_WARNING "KVP: Transaction not active\n");
+		return;
+	}
+	/*
+	 * Copy the global state for completing the transaction. Note that
+	 * only one transaction can be active at a time.
+	 */
+
+	buf = recv_buffer;
+	buf_len = recv_len;
+	channel = recv_channel;
+	req_id = recv_req_id;
+
+	kvp_transaction_active = false;
+
+	icmsghdrp = (struct icmsg_hdr *)&buf[sizeof(struct vmbuspipe_hdr)];
+	kvp_msg = (ic_kvp_msg_t *)&buf[sizeof(struct vmbuspipe_hdr) +
+					sizeof(struct icmsg_hdr)];
+	kvp_data = &kvp_msg->kvp_data;
+	key_name = kvp_keys[key];
+
+	/*
+	 * The windows host expects the key/value pair to be encoded
+	 * in utf16.
+	 */
+	keylen = utf8s_to_utf16s(key_name, strlen(key_name),
+				(wchar_t *)kvp_data->data.key);
+	kvp_data->data.key_size = 2*(keylen + 1); /* utf16 encoding */
+	valuelen = utf8s_to_utf16s(value, strlen(value),
+				(wchar_t *)kvp_data->data.value);
+	kvp_data->data.value_size = 2*(valuelen + 1); /* utf16 encoding */
+
+	kvp_data->data.value_type = REG_SZ; /* all our values are strings */
+	icmsghdrp->status = HV_S_OK;
+
+	icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
+
+	vmbus_sendpacket(channel, buf, buf_len, req_id,
+				VmbusPacketTypeDataInBand, 0);
+
+	/*
+	 * Free up the buffer that was allocatted when we received a message
+	 * from the host.
+	 */
+	kfree(buf);
+}
+
+/*
+ * This callback is invoked when we get a KVP message from the host.
+ * The host ensures that only one KVP transaction can be active at a time.
+ * KVP implementation in Linux needs to forward the key to a user-mde
+ * component to retrive the corresponding value. Consequently, we cannot
+ * respond to the host in the conext of this callback. Since the host
+ * guarantees that at most only one transaction can be active at a time,
+ * we stash away the transaction state in a set of global variables.
+ */
+
+void kvp_onchannelcallback(void *context)
+{
+	struct vmbus_channel *channel = context;
+	u8 *buf;
+	u32 buflen, recvlen;
+	u64 requestid;
+
+	ic_kvp_msg_t *kvp_msg;
+	ic_kvp_msg_enumerate_t *kvp_data;
+
+	struct icmsg_hdr *icmsghdrp;
+	struct icmsg_negotiate *negop = NULL;
+
+
+	buflen = PAGE_SIZE;
+	buf = kmalloc(buflen, GFP_ATOMIC);
+
+	vmbus_recvpacket(channel, buf, buflen, &recvlen, &requestid);
+
+	if (recvlen > 0) {
+		DPRINT_DBG(VMBUS, "KVP packet: len=%d, requestid=%lld",
+			   recvlen, requestid);
+
+		icmsghdrp = (struct icmsg_hdr *)&buf[
+			sizeof(struct vmbuspipe_hdr)];
+
+		if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
+			prep_negotiate_resp(icmsghdrp, negop, buf);
+		} else {
+			kvp_msg = (ic_kvp_msg_t *)&buf[
+				sizeof(struct vmbuspipe_hdr) +
+				sizeof(struct icmsg_hdr)];
+
+			kvp_data = &kvp_msg->kvp_data;
+
+			/*
+			 * We only support the "get" operation on
+			 * "ICKvpExchangePoolAuto" pool.
+			 */
+
+			if ((kvp_msg->kvp_hdr.pool != ICKvpExchangePoolAuto) ||
+				(kvp_msg->kvp_hdr.operation !=
+				ICKvpExchangeOperationEnumerate) ||
+				kvp_transaction_active) {
+				if (kvp_transaction_active)
+					printk(KERN_WARNING
+						"KVP: Invalid call\n");
+				icmsghdrp->status = HV_E_FAIL;
+				goto callback_done;
+			}
+
+			/*
+			 * Stash away this global state for completing the
+			 * transaction; note transactions are serialized.
+			 */
+			recv_buffer = buf;
+			recv_len = recvlen;
+			recv_channel = channel;
+			recv_req_id = requestid;
+
+			switch (kvp_data->index) {
+			case (KVP_MAX_KEY):
+				/*
+				 * We don't support this key
+				 * and any key beyond this.
+				 */
+				icmsghdrp->status = HV_E_FAIL;
+				goto callback_done;
+
+			case (KVP_LIC_VERSION):
+				kvp_transaction_active = true;
+				kvp_respond_to_host(kvp_data->index,
+						HV_DRV_VERSION);
+				return;
+			default:
+				/*
+				 * Get the information from the
+				 * user-mode component.
+				 * component. This transaction will be
+				 * completed when we get the value from
+				 * the user-mode component.
+				 * Set a timeout to deal with
+				 * user-mode not responding.
+				 */
+				kvp_current_index = kvp_data->index;
+				mod_timer(&kvp_timer, jiffies+75);
+				kvp_transaction_active = true;
+				kvp_send_key(kvp_data->index);
+				return;
+			}
+
+		}
+
+callback_done:
+
+		icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
+			| ICMSGHDRFLAG_RESPONSE;
+
+		vmbus_sendpacket(channel, buf,
+				       recvlen, requestid,
+				       VmbusPacketTypeDataInBand, 0);
+	}
+
+	kfree(buf);
+
+
+}
+
+int
+kvp_init(void)
+{
+	int err;
+
+	err = cn_add_callback(&kvp_id, kvp_name, kvp_cn_callback);
+	if (err)
+		return err;
+
+	setup_timer(&kvp_timer, kvp_timer_func,
+		(unsigned long)&kvp_current_index);
+	return 0;
+}
+
+void kvp_deinit(void)
+{
+	cn_del_callback(&kvp_id);
+	del_timer_sync(&kvp_timer);
+}
+
Index: linux.trees.git/drivers/staging/hv/Makefile
===================================================================
--- linux.trees.git.orig/drivers/staging/hv/Makefile	2010-11-10 14:01:55.000000000 -0500
+++ linux.trees.git/drivers/staging/hv/Makefile	2010-11-11 11:24:54.000000000 -0500
@@ -2,7 +2,7 @@  obj-$(CONFIG_HYPERV)		+= hv_vmbus.o hv_t
 obj-$(CONFIG_HYPERV_STORAGE)	+= hv_storvsc.o
 obj-$(CONFIG_HYPERV_BLOCK)	+= hv_blkvsc.o
 obj-$(CONFIG_HYPERV_NET)	+= hv_netvsc.o
-obj-$(CONFIG_HYPERV_UTILS)	+= hv_utils.o
+obj-$(CONFIG_HYPERV_UTILS)	+= hv_util.o
 
 hv_vmbus-y := vmbus_drv.o osd.o \
 		 vmbus.o hv.o connection.o channel.o \
@@ -10,3 +10,4 @@  hv_vmbus-y := vmbus_drv.o osd.o \
 hv_storvsc-y := storvsc_drv.o storvsc.o
 hv_blkvsc-y := blkvsc_drv.o blkvsc.o
 hv_netvsc-y := netvsc_drv.o netvsc.o rndis_filter.o
+hv_util-y :=  hv_utils.o kvp.o
Index: linux.trees.git/drivers/staging/hv/kvp.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux.trees.git/drivers/staging/hv/kvp.h	2010-11-10 14:03:47.000000000 -0500
@@ -0,0 +1,101 @@ 
+/*
+ * An implementation of key value pair (KVP) functionality for Linux.
+ *
+ *
+ * Copyright (C) 2010, Novell, Inc.
+ * Author : K. Y. Srinivasan <ksrinivasan@novell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#ifndef	_KVP_H
+#define	_KVP_H_
+
+/*
+ * Maximum value size - used for both key names and value data, and includes
+ * any applicable NULL terminators.
+ *
+ * Note:  This limit is somewhat arbitrary, but falls easily within what is
+ * supported for all native guests (back to Win 2000) and what is reasonable
+ * for the IC KVP exchange functionality.  Note that Windows Me/98/95 are
+ * limited to 255 character key names.
+ *
+ * MSDN recommends not storing data values larger than 2048 bytes in the
+ * registry.
+ *
+ * Note:  This value is used in defining the KVP exchange message - this value
+ * cannot be modified without affecting the message size and compatability.
+ */
+
+/*
+ * bytes, including any null terminators
+ */
+#define IC_KVP_EXCHANGE_MAX_VALUE_SIZE          (2048)
+
+
+/*
+ * Maximum key size - the registry limit for the length of an entry name
+ * is 256 characters, including the null terminator
+ */
+
+#define IC_KVP_EXCHANGE_MAX_KEY_SIZE            (512)
+
+
+typedef enum {
+	ICKvpExchangeOperationGet = 0,
+	ICKvpExchangeOperationSet,
+	ICKvpExchangeOperationDelete,
+	ICKvpExchangeOperationEnumerate,
+	ICKvpExchangeOperationCount /* Number of operations, must be last. */
+} IC_KVP_EXCHANGE_OPERATION;
+
+typedef enum {
+	ICKvpExchangePoolExternal = 0,
+	ICKvpExchangePoolGuest,
+	ICKvpExchangePoolAuto,
+	ICKvpExchangePoolAutoExternal,
+	ICKvpExchangePoolInternal,
+	ICKvpExchangePoolCount /* Number of pools, must be last. */
+} IC_KVP_EXCHANGE_POOL;
+
+typedef struct ic_kvp_hdr {
+	u8 operation;
+	u8 pool;
+} ic_kvp_hdr_t;
+
+typedef struct ic_kvp_exchg_msg_value {
+	u32 value_type;
+	u32 key_size;
+	u32 value_size;
+	u8 key[IC_KVP_EXCHANGE_MAX_KEY_SIZE];
+	u8 value[IC_KVP_EXCHANGE_MAX_VALUE_SIZE];
+} ic_kvp_exchg_msg_value_t;
+
+typedef struct ic_kvp__msg_enumerate {
+	u32 index;
+	ic_kvp_exchg_msg_value_t data;
+} ic_kvp_msg_enumerate_t;
+
+typedef struct ic_kvp_msg {
+	ic_kvp_hdr_t	kvp_hdr;
+	ic_kvp_msg_enumerate_t	kvp_data;
+} ic_kvp_msg_t;
+
+int kvp_init(void);
+void kvp_deinit(void);
+void kvp_onchannelcallback(void *);
+
+#endif	/* _KVP_H */
+
Index: linux.trees.git/drivers/staging/hv/utils.h
===================================================================
--- linux.trees.git.orig/drivers/staging/hv/utils.h	2010-11-10 10:30:23.000000000 -0500
+++ linux.trees.git/drivers/staging/hv/utils.h	2010-11-10 14:03:47.000000000 -0500
@@ -102,6 +102,7 @@  struct ictimesync_data{
 #define HV_SHUTDOWN_MSG		0
 #define HV_TIMESYNC_MSG		1
 #define HV_HEARTBEAT_MSG	2
+#define HV_KVP_MSG		3
 
 struct hyperv_service_callback {
 	u8 msg_type;
Index: linux.trees.git/drivers/staging/hv/hv_utils.c
===================================================================
--- linux.trees.git.orig/drivers/staging/hv/hv_utils.c	2010-11-10 14:02:40.000000000 -0500
+++ linux.trees.git/drivers/staging/hv/hv_utils.c	2010-11-11 13:42:38.000000000 -0500
@@ -37,6 +37,7 @@ 
 #include "vmbus_private.h"
 #include "vmbus_api.h"
 #include "utils.h"
+#include "kvp.h"
 
 
 static void shutdown_onchannelcallback(void *context)
@@ -268,6 +269,9 @@  static int __init init_hyperv_utils(void
 {
 	printk(KERN_INFO "Registering HyperV Utility Driver\n");
 
+	if (kvp_init())
+		return -ENODEV;
+
 	if (!dmi_check_system(hv_utils_dmi_table))
 		return -ENODEV;
 
@@ -283,6 +287,10 @@  static int __init init_hyperv_utils(void
 		&heartbeat_onchannelcallback;
 	hv_cb_utils[HV_HEARTBEAT_MSG].callback = &heartbeat_onchannelcallback;
 
+	hv_cb_utils[HV_KVP_MSG].channel->OnChannelCallback =
+		&kvp_onchannelcallback;
+
+
 	return 0;
 }
 
@@ -301,6 +309,12 @@  static void exit_hyperv_utils(void)
 	hv_cb_utils[HV_HEARTBEAT_MSG].channel->OnChannelCallback =
 		&chn_cb_negotiate;
 	hv_cb_utils[HV_HEARTBEAT_MSG].callback = &chn_cb_negotiate;
+
+	hv_cb_utils[HV_KVP_MSG].channel->OnChannelCallback =
+		&chn_cb_negotiate;
+
+	kvp_deinit();
+
 }
 
 module_init(init_hyperv_utils);
Index: linux.trees.git/include/linux/connector.h
===================================================================
--- linux.trees.git.orig/include/linux/connector.h	2010-11-09 17:22:15.000000000 -0500
+++ linux.trees.git/include/linux/connector.h	2010-11-11 13:14:52.000000000 -0500
@@ -42,8 +42,9 @@ 
 #define CN_VAL_DM_USERSPACE_LOG		0x1
 #define CN_IDX_DRBD			0x8
 #define CN_VAL_DRBD			0x1
+#define CN_KVP_IDX			0x9     /* MSFT KVP functionality */
 
-#define CN_NETLINK_USERS		8
+#define CN_NETLINK_USERS		10
 
 /*
  * Maximum connector's message size.