diff options
Diffstat (limited to 'drivers/hv/hv_kvp.c')
-rw-r--r-- | drivers/hv/hv_kvp.c | 253 |
1 files changed, 226 insertions, 27 deletions
diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index 0012eed6d872..ed50e9e83c61 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -48,13 +48,24 @@ static struct { void *kvp_context; /* for the channel callback */ } kvp_transaction; +/* + * Before we can accept KVP messages from the host, we need + * to handshake with the user level daemon. This state tracks + * if we are in the handshake phase. + */ +static bool in_hand_shake = true; + +/* + * This state maintains the version number registered by the daemon. + */ +static int dm_reg_value; + static void kvp_send_key(struct work_struct *dummy); -#define TIMEOUT_FIRED 1 -static void kvp_respond_to_host(char *key, char *value, int error); +static void kvp_respond_to_host(struct hv_kvp_msg *msg, int error); static void kvp_work_func(struct work_struct *dummy); -static void kvp_register(void); +static void kvp_register(int); static DECLARE_DELAYED_WORK(kvp_work, kvp_work_func); static DECLARE_WORK(kvp_sendkey_work, kvp_send_key); @@ -68,7 +79,7 @@ static u8 *recv_buffer; */ static void -kvp_register(void) +kvp_register(int reg_value) { struct cn_msg *msg; @@ -83,7 +94,7 @@ kvp_register(void) msg->id.idx = CN_KVP_IDX; msg->id.val = CN_KVP_VAL; - kvp_msg->kvp_hdr.operation = KVP_OP_REGISTER; + kvp_msg->kvp_hdr.operation = reg_value; strcpy(version, HV_DRV_VERSION); msg->len = sizeof(struct hv_kvp_msg); cn_netlink_send(msg, 0, GFP_ATOMIC); @@ -97,9 +108,43 @@ kvp_work_func(struct work_struct *dummy) * If the timer fires, the user-mode component has not responded; * process the pending transaction. */ - kvp_respond_to_host("Unknown key", "Guest timed out", TIMEOUT_FIRED); + kvp_respond_to_host(NULL, HV_E_FAIL); +} + +static int kvp_handle_handshake(struct hv_kvp_msg *msg) +{ + int ret = 1; + + switch (msg->kvp_hdr.operation) { + case KVP_OP_REGISTER: + dm_reg_value = KVP_OP_REGISTER; + pr_info("KVP: IP injection functionality not available\n"); + pr_info("KVP: Upgrade the KVP daemon\n"); + break; + case KVP_OP_REGISTER1: + dm_reg_value = KVP_OP_REGISTER1; + break; + default: + pr_info("KVP: incompatible daemon\n"); + pr_info("KVP: KVP version: %d, Daemon version: %d\n", + KVP_OP_REGISTER1, msg->kvp_hdr.operation); + ret = 0; + } + + if (ret) { + /* + * We have a compatible daemon; complete the handshake. + */ + pr_info("KVP: user-mode registering done.\n"); + kvp_register(dm_reg_value); + kvp_transaction.active = false; + if (kvp_transaction.kvp_context) + hv_kvp_onchannelcallback(kvp_transaction.kvp_context); + } + return ret; } + /* * Callback when data is received from user mode. */ @@ -109,29 +154,165 @@ kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) { struct hv_kvp_msg *message; struct hv_kvp_msg_enumerate *data; + int error = 0; message = (struct hv_kvp_msg *)msg->data; - switch (message->kvp_hdr.operation) { + + /* + * If we are negotiating the version information + * with the daemon; handle that first. + */ + + if (in_hand_shake) { + if (kvp_handle_handshake(message)) + in_hand_shake = false; + return; + } + + /* + * Based on the version of the daemon, we propagate errors from the + * daemon differently. + */ + + data = &message->body.kvp_enum_data; + + switch (dm_reg_value) { 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); + /* + * Null string is used to pass back error condition. + */ + if (data->data.key[0] == 0) + error = HV_S_CONT; break; - default: - data = &message->body.kvp_enum_data; + case KVP_OP_REGISTER1: /* - * Complete the transaction by forwarding the key value - * to the host. But first, cancel the timeout. + * We use the message header information from + * the user level daemon to transmit errors. */ - if (cancel_delayed_work_sync(&kvp_work)) - kvp_respond_to_host(data->data.key, - data->data.value, - !strlen(data->data.key)); + error = message->error; + break; + } + + /* + * Complete the transaction by forwarding the key value + * to the host. But first, cancel the timeout. + */ + if (cancel_delayed_work_sync(&kvp_work)) + kvp_respond_to_host(message, error); +} + + +static int process_ob_ipinfo(void *in_msg, void *out_msg, int op) +{ + struct hv_kvp_msg *in = in_msg; + struct hv_kvp_ip_msg *out = out_msg; + int len; + + switch (op) { + case KVP_OP_GET_IP_INFO: + /* + * Transform all parameters into utf16 encoding. + */ + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.ip_addr, + strlen((char *)in->body.kvp_ip_val.ip_addr), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.sub_net, + strlen((char *)in->body.kvp_ip_val.sub_net), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.gate_way, + strlen((char *)in->body.kvp_ip_val.gate_way), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.dns_addr, + strlen((char *)in->body.kvp_ip_val.dns_addr), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + len = utf8s_to_utf16s((char *)in->body.kvp_ip_val.adapter_id, + strlen((char *)in->body.kvp_ip_val.adapter_id), + UTF16_HOST_ENDIAN, + (wchar_t *)out->kvp_ip_val.adapter_id, + MAX_IP_ADDR_SIZE); + if (len < 0) + return len; + + out->kvp_ip_val.dhcp_enabled = + in->body.kvp_ip_val.dhcp_enabled; + out->kvp_ip_val.addr_family = + in->body.kvp_ip_val.addr_family; + } + + return 0; +} + +static void process_ib_ipinfo(void *in_msg, void *out_msg, int op) +{ + struct hv_kvp_ip_msg *in = in_msg; + struct hv_kvp_msg *out = out_msg; + + switch (op) { + case KVP_OP_SET_IP_INFO: + /* + * Transform all parameters into utf8 encoding. + */ + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE); + + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE); + + out->body.kvp_ip_val.dhcp_enabled = in->kvp_ip_val.dhcp_enabled; + + default: + utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE, + UTF16_LITTLE_ENDIAN, + (__u8 *)out->body.kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE); + + out->body.kvp_ip_val.addr_family = in->kvp_ip_val.addr_family; } } + + + static void kvp_send_key(struct work_struct *dummy) { @@ -167,6 +348,12 @@ kvp_send_key(struct work_struct *dummy) */ switch (message->kvp_hdr.operation) { + case KVP_OP_SET_IP_INFO: + process_ib_ipinfo(in_msg, message, KVP_OP_SET_IP_INFO); + break; + case KVP_OP_GET_IP_INFO: + process_ib_ipinfo(in_msg, message, KVP_OP_GET_IP_INFO); + break; case KVP_OP_SET: switch (in_msg->body.kvp_set.data.value_type) { case REG_SZ: @@ -243,17 +430,19 @@ kvp_send_key(struct work_struct *dummy) */ static void -kvp_respond_to_host(char *key, char *value, int error) +kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error) { struct hv_kvp_msg *kvp_msg; struct hv_kvp_exchg_msg_value *kvp_data; char *key_name; + char *value; struct icmsg_hdr *icmsghdrp; int keylen = 0; int valuelen = 0; u32 buf_len; struct vmbus_channel *channel; u64 req_id; + int ret; /* * If a transaction is not active; log and return. @@ -287,6 +476,7 @@ kvp_respond_to_host(char *key, char *value, int error) */ return; + icmsghdrp->status = error; /* * If the error parameter is set, terminate the host's enumeration @@ -294,20 +484,27 @@ kvp_respond_to_host(char *key, char *value, int error) */ if (error) { /* - * Something failed or the we have timedout; - * terminate the current host-side iteration. + * Something failed or we have timedout; + * terminate the current host-side iteration. */ - icmsghdrp->status = HV_S_CONT; 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_IP_INFO: + ret = process_ob_ipinfo(msg_to_host, + (struct hv_kvp_ip_msg *)kvp_msg, + KVP_OP_GET_IP_INFO); + if (ret < 0) + icmsghdrp->status = HV_E_FAIL; + + goto response_done; + case KVP_OP_SET_IP_INFO: + goto response_done; case KVP_OP_GET: kvp_data = &kvp_msg->body.kvp_get.data; goto copy_value; @@ -321,7 +518,7 @@ kvp_respond_to_host(char *key, char *value, int error) } kvp_data = &kvp_msg->body.kvp_enum_data.data; - key_name = key; + key_name = msg_to_host->body.kvp_enum_data.data.key; /* * The windows host expects the key/value pair to be encoded @@ -335,6 +532,7 @@ kvp_respond_to_host(char *key, char *value, int error) kvp_data->key_size = 2*(keylen + 1); /* utf16 encoding */ copy_value: + value = msg_to_host->body.kvp_enum_data.data.value; valuelen = utf8s_to_utf16s(value, strlen(value), UTF16_HOST_ENDIAN, (wchar_t *) kvp_data->value, (HV_KVP_EXCHANGE_MAX_VALUE_SIZE / 2) - 2); @@ -387,7 +585,8 @@ void hv_kvp_onchannelcallback(void *context) return; } - vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE, &recvlen, &requestid); + vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 2, &recvlen, + &requestid); if (recvlen > 0) { icmsghdrp = (struct icmsg_hdr *)&recv_buffer[ |