summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--drivers/hv/hv_kvp.c218
-rw-r--r--include/linux/hyperv.h2
-rw-r--r--tools/hv/hv_kvp_daemon.c7
3 files changed, 176 insertions, 51 deletions
diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c
index 779109b6f4f0..cfe60b02e3e8 100644
--- a/drivers/hv/hv_kvp.c
+++ b/drivers/hv/hv_kvp.c
@@ -42,9 +42,10 @@
 static struct {
 	bool active; /* transaction status - active or not */
 	int recv_len; /* number of bytes received. */
-	int index; /* current index */
+	struct hv_kvp_msg  *kvp_msg; /* current message */
 	struct vmbus_channel *recv_channel; /* chn we got the request */
 	u64 recv_req_id; /* request ID. */
+	void *kvp_context; /* for the channel callback */
 } kvp_transaction;
 
 static void kvp_send_key(struct work_struct *dummy);
@@ -110,12 +111,15 @@ kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
 	struct hv_kvp_msg_enumerate *data;
 
 	message = (struct hv_kvp_msg *)msg->data;
-	if (message->kvp_hdr.operation == KVP_OP_REGISTER) {
+	switch (message->kvp_hdr.operation) {
+	case KVP_OP_REGISTER:
 		pr_info("KVP: user-mode registering done.\n");
 		kvp_register();
-	}
+		kvp_transaction.active = false;
+		hv_kvp_onchannelcallback(kvp_transaction.kvp_context);
+		break;
 
-	if (message->kvp_hdr.operation == KVP_OP_ENUMERATE) {
+	default:
 		data = &message->body.kvp_enum_data;
 		/*
 		 * Complete the transaction by forwarding the key value
@@ -133,21 +137,104 @@ kvp_send_key(struct work_struct *dummy)
 {
 	struct cn_msg *msg;
 	struct hv_kvp_msg *message;
-	int index = kvp_transaction.index;
+	struct hv_kvp_msg *in_msg;
+	__u8 operation = kvp_transaction.kvp_msg->kvp_hdr.operation;
+	__u8 pool = kvp_transaction.kvp_msg->kvp_hdr.pool;
+	__u32 val32;
+	__u64 val64;
 
 	msg = kzalloc(sizeof(*msg) + sizeof(struct hv_kvp_msg) , GFP_ATOMIC);
+	if (!msg)
+		return;
 
-	if (msg) {
-		msg->id.idx =  CN_KVP_IDX;
-		msg->id.val = CN_KVP_VAL;
+	msg->id.idx =  CN_KVP_IDX;
+	msg->id.val = CN_KVP_VAL;
 
-		message = (struct hv_kvp_msg *)msg->data;
-		message->kvp_hdr.operation = KVP_OP_ENUMERATE;
-		message->body.kvp_enum_data.index = index;
-		msg->len = sizeof(struct hv_kvp_msg);
-		cn_netlink_send(msg, 0, GFP_ATOMIC);
-		kfree(msg);
+	message = (struct hv_kvp_msg *)msg->data;
+	message->kvp_hdr.operation = operation;
+	message->kvp_hdr.pool = pool;
+	in_msg = kvp_transaction.kvp_msg;
+
+	/*
+	 * The key/value strings sent from the host are encoded in
+	 * in utf16; convert it to utf8 strings.
+	 * The host assures us that the utf16 strings will not exceed
+	 * the max lengths specified. We will however, reserve room
+	 * for the string terminating character - in the utf16s_utf8s()
+	 * function we limit the size of the buffer where the converted
+	 * string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to gaurantee
+	 * that the strings can be properly terminated!
+	 */
+
+	switch (message->kvp_hdr.operation) {
+	case KVP_OP_SET:
+		switch (in_msg->body.kvp_set.data.value_type) {
+		case REG_SZ:
+			/*
+			 * The value is a string - utf16 encoding.
+			 */
+			message->body.kvp_set.data.value_size =
+				utf16s_to_utf8s(
+				(wchar_t *)in_msg->body.kvp_set.data.value,
+				in_msg->body.kvp_set.data.value_size,
+				UTF16_LITTLE_ENDIAN,
+				message->body.kvp_set.data.value,
+				HV_KVP_EXCHANGE_MAX_VALUE_SIZE - 1) + 1;
+				break;
+
+		case REG_U32:
+			/*
+			 * The value is a 32 bit scalar.
+			 * We save this as a utf8 string.
+			 */
+			val32 = in_msg->body.kvp_set.data.value_u32;
+			message->body.kvp_set.data.value_size =
+				sprintf(message->body.kvp_set.data.value,
+					"%d", val32) + 1;
+			break;
+
+		case REG_U64:
+			/*
+			 * The value is a 64 bit scalar.
+			 * We save this as a utf8 string.
+			 */
+			val64 = in_msg->body.kvp_set.data.value_u64;
+			message->body.kvp_set.data.value_size =
+				sprintf(message->body.kvp_set.data.value,
+					"%llu", val64) + 1;
+			break;
+
+		}
+	case KVP_OP_GET:
+		message->body.kvp_set.data.key_size =
+			utf16s_to_utf8s(
+			(wchar_t *)in_msg->body.kvp_set.data.key,
+			in_msg->body.kvp_set.data.key_size,
+			UTF16_LITTLE_ENDIAN,
+			message->body.kvp_set.data.key,
+			HV_KVP_EXCHANGE_MAX_KEY_SIZE - 1) + 1;
+			break;
+
+	case KVP_OP_DELETE:
+		message->body.kvp_delete.key_size =
+			utf16s_to_utf8s(
+			(wchar_t *)in_msg->body.kvp_delete.key,
+			in_msg->body.kvp_delete.key_size,
+			UTF16_LITTLE_ENDIAN,
+			message->body.kvp_delete.key,
+			HV_KVP_EXCHANGE_MAX_KEY_SIZE - 1) + 1;
+			break;
+
+	case KVP_OP_ENUMERATE:
+		message->body.kvp_enum_data.index =
+			in_msg->body.kvp_enum_data.index;
+			break;
 	}
+
+	msg->len = sizeof(struct hv_kvp_msg);
+	cn_netlink_send(msg, 0, GFP_ATOMIC);
+	kfree(msg);
+
 	return;
 }
 
@@ -159,10 +246,11 @@ static void
 kvp_respond_to_host(char *key, char *value, int error)
 {
 	struct hv_kvp_msg  *kvp_msg;
-	struct hv_kvp_msg_enumerate  *kvp_data;
+	struct hv_kvp_exchg_msg_value  *kvp_data;
 	char	*key_name;
 	struct icmsg_hdr *icmsghdrp;
-	int	keylen, valuelen;
+	int	keylen = 0;
+	int	valuelen = 0;
 	u32	buf_len;
 	struct vmbus_channel *channel;
 	u64	req_id;
@@ -189,6 +277,9 @@ kvp_respond_to_host(char *key, char *value, int error)
 
 	kvp_transaction.active = false;
 
+	icmsghdrp = (struct icmsg_hdr *)
+			&recv_buffer[sizeof(struct vmbuspipe_hdr)];
+
 	if (channel->onchannel_callback == NULL)
 		/*
 		 * We have raced with util driver being unloaded;
@@ -196,41 +287,66 @@ kvp_respond_to_host(char *key, char *value, int error)
 		 */
 		return;
 
-	icmsghdrp = (struct icmsg_hdr *)
-			&recv_buffer[sizeof(struct vmbuspipe_hdr)];
-	kvp_msg = (struct hv_kvp_msg *)
-			&recv_buffer[sizeof(struct vmbuspipe_hdr) +
-			sizeof(struct icmsg_hdr)];
-	kvp_data = &kvp_msg->body.kvp_enum_data;
-	key_name = key;
 
 	/*
 	 * If the error parameter is set, terminate the host's enumeration.
 	 */
 	if (error) {
 		/*
-		 * We don't support this index or the we have timedout;
+		 * Something failed or the we have timedout;
 		 * terminate the host-side iteration by returning an error.
 		 */
 		icmsghdrp->status = HV_E_FAIL;
 		goto response_done;
 	}
 
+	icmsghdrp->status = HV_S_OK;
+
+	kvp_msg = (struct hv_kvp_msg *)
+			&recv_buffer[sizeof(struct vmbuspipe_hdr) +
+			sizeof(struct icmsg_hdr)];
+
+	switch (kvp_transaction.kvp_msg->kvp_hdr.operation) {
+	case KVP_OP_GET:
+		kvp_data = &kvp_msg->body.kvp_get.data;
+		goto copy_value;
+
+	case KVP_OP_SET:
+	case KVP_OP_DELETE:
+		goto response_done;
+
+	default:
+		break;
+	}
+
+	kvp_data = &kvp_msg->body.kvp_enum_data.data;
+	key_name = key;
+
 	/*
 	 * The windows host expects the key/value pair to be encoded
-	 * in utf16.
+	 * in utf16. Ensure that the key/value size reported to the host
+	 * will be less than or equal to the MAX size (including the
+	 * terminating character).
 	 */
 	keylen = utf8s_to_utf16s(key_name, strlen(key_name), UTF16_HOST_ENDIAN,
-				(wchar_t *) kvp_data->data.key,
-				HV_KVP_EXCHANGE_MAX_KEY_SIZE / 2);
-	kvp_data->data.key_size = 2*(keylen + 1); /* utf16 encoding */
+				(wchar_t *) kvp_data->key,
+				(HV_KVP_EXCHANGE_MAX_KEY_SIZE / 2) - 2);
+	kvp_data->key_size = 2*(keylen + 1); /* utf16 encoding */
+
+copy_value:
 	valuelen = utf8s_to_utf16s(value, strlen(value), UTF16_HOST_ENDIAN,
-				(wchar_t *) kvp_data->data.value,
-				HV_KVP_EXCHANGE_MAX_VALUE_SIZE / 2);
-	kvp_data->data.value_size = 2*(valuelen + 1); /* utf16 encoding */
+				(wchar_t *) kvp_data->value,
+				(HV_KVP_EXCHANGE_MAX_VALUE_SIZE / 2) - 2);
+	kvp_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;
+	/*
+	 * If the utf8s to utf16s conversion failed; notify host
+	 * of the error.
+	 */
+	if ((keylen < 0) || (valuelen < 0))
+		icmsghdrp->status = HV_E_FAIL;
+
+	kvp_data->value_type = REG_SZ; /* all our values are strings */
 
 response_done:
 	icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
@@ -257,11 +373,18 @@ void hv_kvp_onchannelcallback(void *context)
 	u64 requestid;
 
 	struct hv_kvp_msg *kvp_msg;
-	struct hv_kvp_msg_enumerate *kvp_data;
 
 	struct icmsg_hdr *icmsghdrp;
 	struct icmsg_negotiate *negop = NULL;
 
+	if (kvp_transaction.active) {
+		/*
+		 * We will defer processing this callback once
+		 * the current transaction is complete.
+		 */
+		kvp_transaction.kvp_context = context;
+		return;
+	}
 
 	vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE, &recvlen, &requestid);
 
@@ -276,29 +399,16 @@ void hv_kvp_onchannelcallback(void *context)
 				sizeof(struct vmbuspipe_hdr) +
 				sizeof(struct icmsg_hdr)];
 
-			kvp_data = &kvp_msg->body.kvp_enum_data;
-
-			/*
-			 * We only support the "get" operation on
-			 * "KVP_POOL_AUTO" pool.
-			 */
-
-			if ((kvp_msg->kvp_hdr.pool != KVP_POOL_AUTO) ||
-				(kvp_msg->kvp_hdr.operation !=
-				KVP_OP_ENUMERATE)) {
-				icmsghdrp->status = HV_E_FAIL;
-				goto callback_done;
-			}
-
 			/*
 			 * Stash away this global state for completing the
 			 * transaction; note transactions are serialized.
 			 */
+
 			kvp_transaction.recv_len = recvlen;
 			kvp_transaction.recv_channel = channel;
 			kvp_transaction.recv_req_id = requestid;
 			kvp_transaction.active = true;
-			kvp_transaction.index = kvp_data->index;
+			kvp_transaction.kvp_msg = kvp_msg;
 
 			/*
 			 * Get the information from the
@@ -316,8 +426,6 @@ void hv_kvp_onchannelcallback(void *context)
 
 		}
 
-callback_done:
-
 		icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
 			| ICMSGHDRFLAG_RESPONSE;
 
@@ -338,6 +446,14 @@ hv_kvp_init(struct hv_util_service *srv)
 		return err;
 	recv_buffer = srv->recv_buffer;
 
+	/*
+	 * When this driver loads, the user level daemon that
+	 * processes the host requests may not yet be running.
+	 * Defer processing channel callbacks until the daemon
+	 * has registered.
+	 */
+	kvp_transaction.active = true;
+
 	return 0;
 }
 
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index a2d8c547f91b..e88a979107b5 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -119,6 +119,8 @@
  */
 
 #define REG_SZ 1
+#define REG_U32 4
+#define REG_U64 8
 
 enum hv_kvp_exchg_op {
 	KVP_OP_GET = 0,
diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c
index 00d3f7c099e0..a98878c874be 100644
--- a/tools/hv/hv_kvp_daemon.c
+++ b/tools/hv/hv_kvp_daemon.c
@@ -389,10 +389,16 @@ int main(void)
 			}
 			continue;
 
+		case KVP_OP_SET:
+		case KVP_OP_GET:
+		case KVP_OP_DELETE:
 		default:
 			break;
 		}
 
+		if (hv_msg->kvp_hdr.operation != KVP_OP_ENUMERATE)
+			goto kvp_done;
+
 		hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data;
 		key_name = (char *)hv_msg->body.kvp_enum_data.data.key;
 		key_value = (char *)hv_msg->body.kvp_enum_data.data.value;
@@ -454,6 +460,7 @@ int main(void)
 		 * already in the receive buffer. Update the cn_msg header to
 		 * reflect the key value that has been added to the message
 		 */
+kvp_done:
 
 		incoming_cn_msg->id.idx = CN_KVP_IDX;
 		incoming_cn_msg->id.val = CN_KVP_VAL;