diff options
author | Ky Srinivasan <ksrinivasan@novell.com> | 2010-12-16 20:54:16 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2011-01-20 18:09:26 -0500 |
commit | 245ba56a52a32536a751cc3e60e5602afcfc5fd9 (patch) | |
tree | b1811def2161a3c3cbdfe1eb97abc471e602cb6c /drivers/staging/hv/hv_kvp.c | |
parent | 008c7891858498f6eea3802062f118922f46a527 (diff) |
Staging: hv: Implement key/value pair (KVP)
This is an implementation of the key value/pair (KVP) functionality
for Linux guests hosted on HyperV. This component communicates
with the host to support the KVP functionality.
Cc: Haiyang Zhang <haiyangz@microsoft.com>
Cc: Hank Janssen <hjanssen@microsoft.com>
Signed-off-by: K. Y. Srinivasan <ksrinivasan@novell.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging/hv/hv_kvp.c')
-rw-r--r-- | drivers/staging/hv/hv_kvp.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/drivers/staging/hv/hv_kvp.c b/drivers/staging/hv/hv_kvp.c new file mode 100644 index 00000000000..5458631b09c --- /dev/null +++ b/drivers/staging/hv/hv_kvp.c | |||
@@ -0,0 +1,346 @@ | |||
1 | /* | ||
2 | * An implementation of key value pair (KVP) functionality for Linux. | ||
3 | * | ||
4 | * | ||
5 | * Copyright (C) 2010, Novell, Inc. | ||
6 | * Author : K. Y. Srinivasan <ksrinivasan@novell.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License version 2 as published | ||
10 | * by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | ||
15 | * NON INFRINGEMENT. See the GNU General Public License for more | ||
16 | * details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
21 | * | ||
22 | */ | ||
23 | |||
24 | |||
25 | #include <linux/net.h> | ||
26 | #include <linux/nls.h> | ||
27 | #include <linux/connector.h> | ||
28 | #include <linux/workqueue.h> | ||
29 | |||
30 | #include "logging.h" | ||
31 | #include "osd.h" | ||
32 | #include "vmbus.h" | ||
33 | #include "vmbus_packet_format.h" | ||
34 | #include "vmbus_channel_interface.h" | ||
35 | #include "version_info.h" | ||
36 | #include "channel.h" | ||
37 | #include "vmbus_private.h" | ||
38 | #include "vmbus_api.h" | ||
39 | #include "utils.h" | ||
40 | #include "hv_kvp.h" | ||
41 | |||
42 | |||
43 | |||
44 | /* | ||
45 | * Global state maintained for transaction that is being processed. | ||
46 | * Note that only one transaction can be active at any point in time. | ||
47 | * | ||
48 | * This state is set when we receive a request from the host; we | ||
49 | * cleanup this state when the transaction is completed - when we respond | ||
50 | * to the host with the key value. | ||
51 | */ | ||
52 | |||
53 | static struct { | ||
54 | bool active; /* transaction status - active or not */ | ||
55 | int recv_len; /* number of bytes received. */ | ||
56 | struct vmbus_channel *recv_channel; /* chn we got the request */ | ||
57 | u64 recv_req_id; /* request ID. */ | ||
58 | } kvp_transaction; | ||
59 | |||
60 | static int kvp_send_key(int index); | ||
61 | |||
62 | static void kvp_respond_to_host(char *key, char *value, int error); | ||
63 | static void kvp_work_func(struct work_struct *dummy); | ||
64 | static void kvp_register(void); | ||
65 | |||
66 | static DECLARE_DELAYED_WORK(kvp_work, kvp_work_func); | ||
67 | |||
68 | static struct cb_id kvp_id = { CN_KVP_IDX, CN_KVP_VAL }; | ||
69 | static const char kvp_name[] = "kvp_kernel_module"; | ||
70 | static int timeout_fired; | ||
71 | static u8 *recv_buffer; | ||
72 | /* | ||
73 | * Register the kernel component with the user-level daemon. | ||
74 | * As part of this registration, pass the LIC version number. | ||
75 | */ | ||
76 | |||
77 | static void | ||
78 | kvp_register(void) | ||
79 | { | ||
80 | |||
81 | struct cn_msg *msg; | ||
82 | |||
83 | msg = kzalloc(sizeof(*msg) + strlen(HV_DRV_VERSION) + 1 , GFP_ATOMIC); | ||
84 | |||
85 | if (msg) { | ||
86 | msg->id.idx = CN_KVP_IDX; | ||
87 | msg->id.val = CN_KVP_VAL; | ||
88 | msg->seq = KVP_REGISTER; | ||
89 | strcpy(msg->data, HV_DRV_VERSION); | ||
90 | msg->len = strlen(HV_DRV_VERSION) + 1; | ||
91 | cn_netlink_send(msg, 0, GFP_ATOMIC); | ||
92 | kfree(msg); | ||
93 | } | ||
94 | } | ||
95 | static void | ||
96 | kvp_work_func(struct work_struct *dummy) | ||
97 | { | ||
98 | /* | ||
99 | * If the timer fires, the user-mode component has not responded; | ||
100 | * process the pending transaction. | ||
101 | */ | ||
102 | kvp_respond_to_host("Unknown key", "Guest timed out", timeout_fired); | ||
103 | timeout_fired = 1; | ||
104 | } | ||
105 | |||
106 | /* | ||
107 | * Callback when data is received from user mode. | ||
108 | */ | ||
109 | |||
110 | static void | ||
111 | kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) | ||
112 | { | ||
113 | struct hv_ku_msg *message; | ||
114 | |||
115 | message = (struct hv_ku_msg *)msg->data; | ||
116 | if (msg->seq == KVP_REGISTER) { | ||
117 | printk(KERN_INFO "KVP: user-mode registering done.\n"); | ||
118 | kvp_register(); | ||
119 | } | ||
120 | |||
121 | if (msg->seq == KVP_USER_SET) { | ||
122 | /* | ||
123 | * Complete the transaction by forwarding the key value | ||
124 | * to the host. But first, cancel the timeout. | ||
125 | */ | ||
126 | if (cancel_delayed_work_sync(&kvp_work)) | ||
127 | kvp_respond_to_host(message->kvp_key, | ||
128 | message->kvp_value, | ||
129 | !strlen(message->kvp_key)); | ||
130 | } | ||
131 | } | ||
132 | |||
133 | static int | ||
134 | kvp_send_key(int index) | ||
135 | { | ||
136 | struct cn_msg *msg; | ||
137 | |||
138 | msg = kzalloc(sizeof(*msg) + sizeof(struct hv_kvp_msg) , GFP_ATOMIC); | ||
139 | |||
140 | if (msg) { | ||
141 | msg->id.idx = CN_KVP_IDX; | ||
142 | msg->id.val = CN_KVP_VAL; | ||
143 | msg->seq = KVP_KERNEL_GET; | ||
144 | ((struct hv_ku_msg *)msg->data)->kvp_index = index; | ||
145 | msg->len = sizeof(struct hv_ku_msg); | ||
146 | cn_netlink_send(msg, 0, GFP_ATOMIC); | ||
147 | kfree(msg); | ||
148 | return 0; | ||
149 | } | ||
150 | return 1; | ||
151 | } | ||
152 | |||
153 | /* | ||
154 | * Send a response back to the host. | ||
155 | */ | ||
156 | |||
157 | static void | ||
158 | kvp_respond_to_host(char *key, char *value, int error) | ||
159 | { | ||
160 | struct hv_kvp_msg *kvp_msg; | ||
161 | struct hv_kvp_msg_enumerate *kvp_data; | ||
162 | char *key_name; | ||
163 | struct icmsg_hdr *icmsghdrp; | ||
164 | int keylen, valuelen; | ||
165 | u32 buf_len; | ||
166 | struct vmbus_channel *channel; | ||
167 | u64 req_id; | ||
168 | |||
169 | /* | ||
170 | * If a transaction is not active; log and return. | ||
171 | */ | ||
172 | |||
173 | if (!kvp_transaction.active) { | ||
174 | /* | ||
175 | * This is a spurious call! | ||
176 | */ | ||
177 | printk(KERN_WARNING "KVP: Transaction not active\n"); | ||
178 | return; | ||
179 | } | ||
180 | /* | ||
181 | * Copy the global state for completing the transaction. Note that | ||
182 | * only one transaction can be active at a time. | ||
183 | */ | ||
184 | |||
185 | buf_len = kvp_transaction.recv_len; | ||
186 | channel = kvp_transaction.recv_channel; | ||
187 | req_id = kvp_transaction.recv_req_id; | ||
188 | |||
189 | icmsghdrp = (struct icmsg_hdr *) | ||
190 | &recv_buffer[sizeof(struct vmbuspipe_hdr)]; | ||
191 | kvp_msg = (struct hv_kvp_msg *) | ||
192 | &recv_buffer[sizeof(struct vmbuspipe_hdr) + | ||
193 | sizeof(struct icmsg_hdr)]; | ||
194 | kvp_data = &kvp_msg->kvp_data; | ||
195 | key_name = key; | ||
196 | |||
197 | /* | ||
198 | * If the error parameter is set, terminate the host's enumeration. | ||
199 | */ | ||
200 | if (error) { | ||
201 | /* | ||
202 | * We don't support this index or the we have timedout; | ||
203 | * terminate the host-side iteration by returning an error. | ||
204 | */ | ||
205 | icmsghdrp->status = HV_E_FAIL; | ||
206 | goto response_done; | ||
207 | } | ||
208 | |||
209 | /* | ||
210 | * The windows host expects the key/value pair to be encoded | ||
211 | * in utf16. | ||
212 | */ | ||
213 | keylen = utf8s_to_utf16s(key_name, strlen(key_name), | ||
214 | (wchar_t *)kvp_data->data.key); | ||
215 | kvp_data->data.key_size = 2*(keylen + 1); /* utf16 encoding */ | ||
216 | valuelen = utf8s_to_utf16s(value, strlen(value), | ||
217 | (wchar_t *)kvp_data->data.value); | ||
218 | kvp_data->data.value_size = 2*(valuelen + 1); /* utf16 encoding */ | ||
219 | |||
220 | kvp_data->data.value_type = REG_SZ; /* all our values are strings */ | ||
221 | icmsghdrp->status = HV_S_OK; | ||
222 | |||
223 | response_done: | ||
224 | icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE; | ||
225 | |||
226 | vmbus_sendpacket(channel, recv_buffer, buf_len, req_id, | ||
227 | VmbusPacketTypeDataInBand, 0); | ||
228 | |||
229 | kvp_transaction.active = false; | ||
230 | } | ||
231 | |||
232 | /* | ||
233 | * This callback is invoked when we get a KVP message from the host. | ||
234 | * The host ensures that only one KVP transaction can be active at a time. | ||
235 | * KVP implementation in Linux needs to forward the key to a user-mde | ||
236 | * component to retrive the corresponding value. Consequently, we cannot | ||
237 | * respond to the host in the conext of this callback. Since the host | ||
238 | * guarantees that at most only one transaction can be active at a time, | ||
239 | * we stash away the transaction state in a set of global variables. | ||
240 | */ | ||
241 | |||
242 | void hv_kvp_onchannelcallback(void *context) | ||
243 | { | ||
244 | struct vmbus_channel *channel = context; | ||
245 | u32 recvlen; | ||
246 | u64 requestid; | ||
247 | |||
248 | struct hv_kvp_msg *kvp_msg; | ||
249 | struct hv_kvp_msg_enumerate *kvp_data; | ||
250 | |||
251 | struct icmsg_hdr *icmsghdrp; | ||
252 | struct icmsg_negotiate *negop = NULL; | ||
253 | |||
254 | |||
255 | if (kvp_transaction.active) | ||
256 | return; | ||
257 | |||
258 | |||
259 | vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE, &recvlen, &requestid); | ||
260 | |||
261 | if (recvlen > 0) { | ||
262 | DPRINT_DBG(VMBUS, "KVP packet: len=%d, requestid=%lld", | ||
263 | recvlen, requestid); | ||
264 | |||
265 | icmsghdrp = (struct icmsg_hdr *)&recv_buffer[ | ||
266 | sizeof(struct vmbuspipe_hdr)]; | ||
267 | |||
268 | if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { | ||
269 | prep_negotiate_resp(icmsghdrp, negop, recv_buffer); | ||
270 | } else { | ||
271 | kvp_msg = (struct hv_kvp_msg *)&recv_buffer[ | ||
272 | sizeof(struct vmbuspipe_hdr) + | ||
273 | sizeof(struct icmsg_hdr)]; | ||
274 | |||
275 | kvp_data = &kvp_msg->kvp_data; | ||
276 | |||
277 | /* | ||
278 | * We only support the "get" operation on | ||
279 | * "KVP_POOL_AUTO" pool. | ||
280 | */ | ||
281 | |||
282 | if ((kvp_msg->kvp_hdr.pool != KVP_POOL_AUTO) || | ||
283 | (kvp_msg->kvp_hdr.operation != | ||
284 | KVP_OP_ENUMERATE)) { | ||
285 | icmsghdrp->status = HV_E_FAIL; | ||
286 | goto callback_done; | ||
287 | } | ||
288 | |||
289 | /* | ||
290 | * Stash away this global state for completing the | ||
291 | * transaction; note transactions are serialized. | ||
292 | */ | ||
293 | kvp_transaction.recv_len = recvlen; | ||
294 | kvp_transaction.recv_channel = channel; | ||
295 | kvp_transaction.recv_req_id = requestid; | ||
296 | kvp_transaction.active = true; | ||
297 | |||
298 | /* | ||
299 | * Get the information from the | ||
300 | * user-mode component. | ||
301 | * component. This transaction will be | ||
302 | * completed when we get the value from | ||
303 | * the user-mode component. | ||
304 | * Set a timeout to deal with | ||
305 | * user-mode not responding. | ||
306 | */ | ||
307 | kvp_send_key(kvp_data->index); | ||
308 | schedule_delayed_work(&kvp_work, 100); | ||
309 | |||
310 | return; | ||
311 | |||
312 | } | ||
313 | |||
314 | callback_done: | ||
315 | |||
316 | icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION | ||
317 | | ICMSGHDRFLAG_RESPONSE; | ||
318 | |||
319 | vmbus_sendpacket(channel, recv_buffer, | ||
320 | recvlen, requestid, | ||
321 | VmbusPacketTypeDataInBand, 0); | ||
322 | } | ||
323 | |||
324 | } | ||
325 | |||
326 | int | ||
327 | hv_kvp_init(void) | ||
328 | { | ||
329 | int err; | ||
330 | |||
331 | err = cn_add_callback(&kvp_id, kvp_name, kvp_cn_callback); | ||
332 | if (err) | ||
333 | return err; | ||
334 | recv_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); | ||
335 | if (!recv_buffer) | ||
336 | return -ENOMEM; | ||
337 | |||
338 | return 0; | ||
339 | } | ||
340 | |||
341 | void hv_kvp_deinit(void) | ||
342 | { | ||
343 | cn_del_callback(&kvp_id); | ||
344 | cancel_delayed_work_sync(&kvp_work); | ||
345 | kfree(recv_buffer); | ||
346 | } | ||