diff options
Diffstat (limited to 'drivers/hid/hid-hyperv.c')
-rw-r--r-- | drivers/hid/hid-hyperv.c | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c new file mode 100644 index 000000000000..406632472c1b --- /dev/null +++ b/drivers/hid/hid-hyperv.c | |||
@@ -0,0 +1,587 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2009, Citrix Systems, Inc. | ||
3 | * Copyright (c) 2010, Microsoft Corporation. | ||
4 | * Copyright (c) 2011, Novell Inc. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms and conditions of the GNU General Public License, | ||
8 | * version 2, as published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | */ | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/device.h> | ||
18 | #include <linux/completion.h> | ||
19 | #include <linux/input.h> | ||
20 | #include <linux/hid.h> | ||
21 | #include <linux/hiddev.h> | ||
22 | #include <linux/hyperv.h> | ||
23 | |||
24 | |||
25 | struct hv_input_dev_info { | ||
26 | unsigned int size; | ||
27 | unsigned short vendor; | ||
28 | unsigned short product; | ||
29 | unsigned short version; | ||
30 | unsigned short reserved[11]; | ||
31 | }; | ||
32 | |||
33 | /* The maximum size of a synthetic input message. */ | ||
34 | #define SYNTHHID_MAX_INPUT_REPORT_SIZE 16 | ||
35 | |||
36 | /* | ||
37 | * Current version | ||
38 | * | ||
39 | * History: | ||
40 | * Beta, RC < 2008/1/22 1,0 | ||
41 | * RC > 2008/1/22 2,0 | ||
42 | */ | ||
43 | #define SYNTHHID_INPUT_VERSION_MAJOR 2 | ||
44 | #define SYNTHHID_INPUT_VERSION_MINOR 0 | ||
45 | #define SYNTHHID_INPUT_VERSION (SYNTHHID_INPUT_VERSION_MINOR | \ | ||
46 | (SYNTHHID_INPUT_VERSION_MAJOR << 16)) | ||
47 | |||
48 | |||
49 | #pragma pack(push, 1) | ||
50 | /* | ||
51 | * Message types in the synthetic input protocol | ||
52 | */ | ||
53 | enum synthhid_msg_type { | ||
54 | SYNTH_HID_PROTOCOL_REQUEST, | ||
55 | SYNTH_HID_PROTOCOL_RESPONSE, | ||
56 | SYNTH_HID_INITIAL_DEVICE_INFO, | ||
57 | SYNTH_HID_INITIAL_DEVICE_INFO_ACK, | ||
58 | SYNTH_HID_INPUT_REPORT, | ||
59 | SYNTH_HID_MAX | ||
60 | }; | ||
61 | |||
62 | /* | ||
63 | * Basic message structures. | ||
64 | */ | ||
65 | struct synthhid_msg_hdr { | ||
66 | enum synthhid_msg_type type; | ||
67 | u32 size; | ||
68 | }; | ||
69 | |||
70 | struct synthhid_msg { | ||
71 | struct synthhid_msg_hdr header; | ||
72 | char data[1]; /* Enclosed message */ | ||
73 | }; | ||
74 | |||
75 | union synthhid_version { | ||
76 | struct { | ||
77 | u16 minor_version; | ||
78 | u16 major_version; | ||
79 | }; | ||
80 | u32 version; | ||
81 | }; | ||
82 | |||
83 | /* | ||
84 | * Protocol messages | ||
85 | */ | ||
86 | struct synthhid_protocol_request { | ||
87 | struct synthhid_msg_hdr header; | ||
88 | union synthhid_version version_requested; | ||
89 | }; | ||
90 | |||
91 | struct synthhid_protocol_response { | ||
92 | struct synthhid_msg_hdr header; | ||
93 | union synthhid_version version_requested; | ||
94 | unsigned char approved; | ||
95 | }; | ||
96 | |||
97 | struct synthhid_device_info { | ||
98 | struct synthhid_msg_hdr header; | ||
99 | struct hv_input_dev_info hid_dev_info; | ||
100 | struct hid_descriptor hid_descriptor; | ||
101 | }; | ||
102 | |||
103 | struct synthhid_device_info_ack { | ||
104 | struct synthhid_msg_hdr header; | ||
105 | unsigned char reserved; | ||
106 | }; | ||
107 | |||
108 | struct synthhid_input_report { | ||
109 | struct synthhid_msg_hdr header; | ||
110 | char buffer[1]; | ||
111 | }; | ||
112 | |||
113 | #pragma pack(pop) | ||
114 | |||
115 | #define INPUTVSC_SEND_RING_BUFFER_SIZE (10*PAGE_SIZE) | ||
116 | #define INPUTVSC_RECV_RING_BUFFER_SIZE (10*PAGE_SIZE) | ||
117 | |||
118 | |||
119 | enum pipe_prot_msg_type { | ||
120 | PIPE_MESSAGE_INVALID, | ||
121 | PIPE_MESSAGE_DATA, | ||
122 | PIPE_MESSAGE_MAXIMUM | ||
123 | }; | ||
124 | |||
125 | |||
126 | struct pipe_prt_msg { | ||
127 | enum pipe_prot_msg_type type; | ||
128 | u32 size; | ||
129 | char data[1]; | ||
130 | }; | ||
131 | |||
132 | struct mousevsc_prt_msg { | ||
133 | enum pipe_prot_msg_type type; | ||
134 | u32 size; | ||
135 | union { | ||
136 | struct synthhid_protocol_request request; | ||
137 | struct synthhid_protocol_response response; | ||
138 | struct synthhid_device_info_ack ack; | ||
139 | }; | ||
140 | }; | ||
141 | |||
142 | /* | ||
143 | * Represents an mousevsc device | ||
144 | */ | ||
145 | struct mousevsc_dev { | ||
146 | struct hv_device *device; | ||
147 | bool init_complete; | ||
148 | bool connected; | ||
149 | struct mousevsc_prt_msg protocol_req; | ||
150 | struct mousevsc_prt_msg protocol_resp; | ||
151 | /* Synchronize the request/response if needed */ | ||
152 | struct completion wait_event; | ||
153 | int dev_info_status; | ||
154 | |||
155 | struct hid_descriptor *hid_desc; | ||
156 | unsigned char *report_desc; | ||
157 | u32 report_desc_size; | ||
158 | struct hv_input_dev_info hid_dev_info; | ||
159 | struct hid_device *hid_device; | ||
160 | }; | ||
161 | |||
162 | |||
163 | static struct mousevsc_dev *mousevsc_alloc_device(struct hv_device *device) | ||
164 | { | ||
165 | struct mousevsc_dev *input_dev; | ||
166 | |||
167 | input_dev = kzalloc(sizeof(struct mousevsc_dev), GFP_KERNEL); | ||
168 | |||
169 | if (!input_dev) | ||
170 | return NULL; | ||
171 | |||
172 | input_dev->device = device; | ||
173 | hv_set_drvdata(device, input_dev); | ||
174 | init_completion(&input_dev->wait_event); | ||
175 | input_dev->init_complete = false; | ||
176 | |||
177 | return input_dev; | ||
178 | } | ||
179 | |||
180 | static void mousevsc_free_device(struct mousevsc_dev *device) | ||
181 | { | ||
182 | kfree(device->hid_desc); | ||
183 | kfree(device->report_desc); | ||
184 | hv_set_drvdata(device->device, NULL); | ||
185 | kfree(device); | ||
186 | } | ||
187 | |||
188 | static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device, | ||
189 | struct synthhid_device_info *device_info) | ||
190 | { | ||
191 | int ret = 0; | ||
192 | struct hid_descriptor *desc; | ||
193 | struct mousevsc_prt_msg ack; | ||
194 | |||
195 | input_device->dev_info_status = -ENOMEM; | ||
196 | |||
197 | input_device->hid_dev_info = device_info->hid_dev_info; | ||
198 | desc = &device_info->hid_descriptor; | ||
199 | if (desc->bLength == 0) | ||
200 | goto cleanup; | ||
201 | |||
202 | input_device->hid_desc = kzalloc(desc->bLength, GFP_ATOMIC); | ||
203 | |||
204 | if (!input_device->hid_desc) | ||
205 | goto cleanup; | ||
206 | |||
207 | memcpy(input_device->hid_desc, desc, desc->bLength); | ||
208 | |||
209 | input_device->report_desc_size = desc->desc[0].wDescriptorLength; | ||
210 | if (input_device->report_desc_size == 0) { | ||
211 | input_device->dev_info_status = -EINVAL; | ||
212 | goto cleanup; | ||
213 | } | ||
214 | |||
215 | input_device->report_desc = kzalloc(input_device->report_desc_size, | ||
216 | GFP_ATOMIC); | ||
217 | |||
218 | if (!input_device->report_desc) { | ||
219 | input_device->dev_info_status = -ENOMEM; | ||
220 | goto cleanup; | ||
221 | } | ||
222 | |||
223 | memcpy(input_device->report_desc, | ||
224 | ((unsigned char *)desc) + desc->bLength, | ||
225 | desc->desc[0].wDescriptorLength); | ||
226 | |||
227 | /* Send the ack */ | ||
228 | memset(&ack, 0, sizeof(struct mousevsc_prt_msg)); | ||
229 | |||
230 | ack.type = PIPE_MESSAGE_DATA; | ||
231 | ack.size = sizeof(struct synthhid_device_info_ack); | ||
232 | |||
233 | ack.ack.header.type = SYNTH_HID_INITIAL_DEVICE_INFO_ACK; | ||
234 | ack.ack.header.size = 1; | ||
235 | ack.ack.reserved = 0; | ||
236 | |||
237 | ret = vmbus_sendpacket(input_device->device->channel, | ||
238 | &ack, | ||
239 | sizeof(struct pipe_prt_msg) - sizeof(unsigned char) + | ||
240 | sizeof(struct synthhid_device_info_ack), | ||
241 | (unsigned long)&ack, | ||
242 | VM_PKT_DATA_INBAND, | ||
243 | VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); | ||
244 | |||
245 | if (!ret) | ||
246 | input_device->dev_info_status = 0; | ||
247 | |||
248 | cleanup: | ||
249 | complete(&input_device->wait_event); | ||
250 | |||
251 | return; | ||
252 | } | ||
253 | |||
254 | static void mousevsc_on_receive(struct hv_device *device, | ||
255 | struct vmpacket_descriptor *packet) | ||
256 | { | ||
257 | struct pipe_prt_msg *pipe_msg; | ||
258 | struct synthhid_msg *hid_msg; | ||
259 | struct mousevsc_dev *input_dev = hv_get_drvdata(device); | ||
260 | struct synthhid_input_report *input_report; | ||
261 | |||
262 | pipe_msg = (struct pipe_prt_msg *)((unsigned long)packet + | ||
263 | (packet->offset8 << 3)); | ||
264 | |||
265 | if (pipe_msg->type != PIPE_MESSAGE_DATA) | ||
266 | return; | ||
267 | |||
268 | hid_msg = (struct synthhid_msg *)pipe_msg->data; | ||
269 | |||
270 | switch (hid_msg->header.type) { | ||
271 | case SYNTH_HID_PROTOCOL_RESPONSE: | ||
272 | /* | ||
273 | * While it will be impossible for us to protect against | ||
274 | * malicious/buggy hypervisor/host, add a check here to | ||
275 | * ensure we don't corrupt memory. | ||
276 | */ | ||
277 | if ((pipe_msg->size + sizeof(struct pipe_prt_msg) | ||
278 | - sizeof(unsigned char)) | ||
279 | > sizeof(struct mousevsc_prt_msg)) { | ||
280 | WARN_ON(1); | ||
281 | break; | ||
282 | } | ||
283 | |||
284 | memcpy(&input_dev->protocol_resp, pipe_msg, | ||
285 | pipe_msg->size + sizeof(struct pipe_prt_msg) - | ||
286 | sizeof(unsigned char)); | ||
287 | complete(&input_dev->wait_event); | ||
288 | break; | ||
289 | |||
290 | case SYNTH_HID_INITIAL_DEVICE_INFO: | ||
291 | WARN_ON(pipe_msg->size < sizeof(struct hv_input_dev_info)); | ||
292 | |||
293 | /* | ||
294 | * Parse out the device info into device attr, | ||
295 | * hid desc and report desc | ||
296 | */ | ||
297 | mousevsc_on_receive_device_info(input_dev, | ||
298 | (struct synthhid_device_info *)pipe_msg->data); | ||
299 | break; | ||
300 | case SYNTH_HID_INPUT_REPORT: | ||
301 | input_report = | ||
302 | (struct synthhid_input_report *)pipe_msg->data; | ||
303 | if (!input_dev->init_complete) | ||
304 | break; | ||
305 | hid_input_report(input_dev->hid_device, | ||
306 | HID_INPUT_REPORT, input_report->buffer, | ||
307 | input_report->header.size, 1); | ||
308 | break; | ||
309 | default: | ||
310 | pr_err("unsupported hid msg type - type %d len %d", | ||
311 | hid_msg->header.type, hid_msg->header.size); | ||
312 | break; | ||
313 | } | ||
314 | |||
315 | } | ||
316 | |||
317 | static void mousevsc_on_channel_callback(void *context) | ||
318 | { | ||
319 | const int packet_size = 0x100; | ||
320 | int ret; | ||
321 | struct hv_device *device = context; | ||
322 | u32 bytes_recvd; | ||
323 | u64 req_id; | ||
324 | struct vmpacket_descriptor *desc; | ||
325 | unsigned char *buffer; | ||
326 | int bufferlen = packet_size; | ||
327 | |||
328 | buffer = kmalloc(bufferlen, GFP_ATOMIC); | ||
329 | if (!buffer) | ||
330 | return; | ||
331 | |||
332 | do { | ||
333 | ret = vmbus_recvpacket_raw(device->channel, buffer, | ||
334 | bufferlen, &bytes_recvd, &req_id); | ||
335 | |||
336 | switch (ret) { | ||
337 | case 0: | ||
338 | if (bytes_recvd <= 0) { | ||
339 | kfree(buffer); | ||
340 | return; | ||
341 | } | ||
342 | desc = (struct vmpacket_descriptor *)buffer; | ||
343 | |||
344 | switch (desc->type) { | ||
345 | case VM_PKT_COMP: | ||
346 | break; | ||
347 | |||
348 | case VM_PKT_DATA_INBAND: | ||
349 | mousevsc_on_receive(device, desc); | ||
350 | break; | ||
351 | |||
352 | default: | ||
353 | pr_err("unhandled packet type %d, tid %llx len %d\n", | ||
354 | desc->type, req_id, bytes_recvd); | ||
355 | break; | ||
356 | } | ||
357 | |||
358 | break; | ||
359 | |||
360 | case -ENOBUFS: | ||
361 | kfree(buffer); | ||
362 | /* Handle large packet */ | ||
363 | bufferlen = bytes_recvd; | ||
364 | buffer = kmalloc(bytes_recvd, GFP_ATOMIC); | ||
365 | |||
366 | if (!buffer) | ||
367 | return; | ||
368 | |||
369 | break; | ||
370 | } | ||
371 | } while (1); | ||
372 | |||
373 | } | ||
374 | |||
375 | static int mousevsc_connect_to_vsp(struct hv_device *device) | ||
376 | { | ||
377 | int ret = 0; | ||
378 | int t; | ||
379 | struct mousevsc_dev *input_dev = hv_get_drvdata(device); | ||
380 | struct mousevsc_prt_msg *request; | ||
381 | struct mousevsc_prt_msg *response; | ||
382 | |||
383 | request = &input_dev->protocol_req; | ||
384 | memset(request, 0, sizeof(struct mousevsc_prt_msg)); | ||
385 | |||
386 | request->type = PIPE_MESSAGE_DATA; | ||
387 | request->size = sizeof(struct synthhid_protocol_request); | ||
388 | request->request.header.type = SYNTH_HID_PROTOCOL_REQUEST; | ||
389 | request->request.header.size = sizeof(unsigned int); | ||
390 | request->request.version_requested.version = SYNTHHID_INPUT_VERSION; | ||
391 | |||
392 | ret = vmbus_sendpacket(device->channel, request, | ||
393 | sizeof(struct pipe_prt_msg) - | ||
394 | sizeof(unsigned char) + | ||
395 | sizeof(struct synthhid_protocol_request), | ||
396 | (unsigned long)request, | ||
397 | VM_PKT_DATA_INBAND, | ||
398 | VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); | ||
399 | if (ret) | ||
400 | goto cleanup; | ||
401 | |||
402 | t = wait_for_completion_timeout(&input_dev->wait_event, 5*HZ); | ||
403 | if (!t) { | ||
404 | ret = -ETIMEDOUT; | ||
405 | goto cleanup; | ||
406 | } | ||
407 | |||
408 | response = &input_dev->protocol_resp; | ||
409 | |||
410 | if (!response->response.approved) { | ||
411 | pr_err("synthhid protocol request failed (version %d)\n", | ||
412 | SYNTHHID_INPUT_VERSION); | ||
413 | ret = -ENODEV; | ||
414 | goto cleanup; | ||
415 | } | ||
416 | |||
417 | t = wait_for_completion_timeout(&input_dev->wait_event, 5*HZ); | ||
418 | if (!t) { | ||
419 | ret = -ETIMEDOUT; | ||
420 | goto cleanup; | ||
421 | } | ||
422 | |||
423 | /* | ||
424 | * We should have gotten the device attr, hid desc and report | ||
425 | * desc at this point | ||
426 | */ | ||
427 | ret = input_dev->dev_info_status; | ||
428 | |||
429 | cleanup: | ||
430 | return ret; | ||
431 | } | ||
432 | |||
433 | static int mousevsc_hid_open(struct hid_device *hid) | ||
434 | { | ||
435 | return 0; | ||
436 | } | ||
437 | |||
438 | static int mousevsc_hid_start(struct hid_device *hid) | ||
439 | { | ||
440 | return 0; | ||
441 | } | ||
442 | |||
443 | static void mousevsc_hid_close(struct hid_device *hid) | ||
444 | { | ||
445 | } | ||
446 | |||
447 | static void mousevsc_hid_stop(struct hid_device *hid) | ||
448 | { | ||
449 | } | ||
450 | |||
451 | static struct hid_ll_driver mousevsc_ll_driver = { | ||
452 | .open = mousevsc_hid_open, | ||
453 | .close = mousevsc_hid_close, | ||
454 | .start = mousevsc_hid_start, | ||
455 | .stop = mousevsc_hid_stop, | ||
456 | }; | ||
457 | |||
458 | static struct hid_driver mousevsc_hid_driver; | ||
459 | |||
460 | static int mousevsc_probe(struct hv_device *device, | ||
461 | const struct hv_vmbus_device_id *dev_id) | ||
462 | { | ||
463 | int ret; | ||
464 | struct mousevsc_dev *input_dev; | ||
465 | struct hid_device *hid_dev; | ||
466 | |||
467 | input_dev = mousevsc_alloc_device(device); | ||
468 | |||
469 | if (!input_dev) | ||
470 | return -ENOMEM; | ||
471 | |||
472 | ret = vmbus_open(device->channel, | ||
473 | INPUTVSC_SEND_RING_BUFFER_SIZE, | ||
474 | INPUTVSC_RECV_RING_BUFFER_SIZE, | ||
475 | NULL, | ||
476 | 0, | ||
477 | mousevsc_on_channel_callback, | ||
478 | device | ||
479 | ); | ||
480 | |||
481 | if (ret) | ||
482 | goto probe_err0; | ||
483 | |||
484 | ret = mousevsc_connect_to_vsp(device); | ||
485 | |||
486 | if (ret) | ||
487 | goto probe_err1; | ||
488 | |||
489 | /* workaround SA-167 */ | ||
490 | if (input_dev->report_desc[14] == 0x25) | ||
491 | input_dev->report_desc[14] = 0x29; | ||
492 | |||
493 | hid_dev = hid_allocate_device(); | ||
494 | if (IS_ERR(hid_dev)) { | ||
495 | ret = PTR_ERR(hid_dev); | ||
496 | goto probe_err1; | ||
497 | } | ||
498 | |||
499 | hid_dev->ll_driver = &mousevsc_ll_driver; | ||
500 | hid_dev->driver = &mousevsc_hid_driver; | ||
501 | hid_dev->bus = BUS_VIRTUAL; | ||
502 | hid_dev->vendor = input_dev->hid_dev_info.vendor; | ||
503 | hid_dev->product = input_dev->hid_dev_info.product; | ||
504 | hid_dev->version = input_dev->hid_dev_info.version; | ||
505 | input_dev->hid_device = hid_dev; | ||
506 | |||
507 | sprintf(hid_dev->name, "%s", "Microsoft Vmbus HID-compliant Mouse"); | ||
508 | |||
509 | ret = hid_add_device(hid_dev); | ||
510 | if (ret) | ||
511 | goto probe_err1; | ||
512 | |||
513 | ret = hid_parse_report(hid_dev, input_dev->report_desc, | ||
514 | input_dev->report_desc_size); | ||
515 | |||
516 | if (ret) { | ||
517 | hid_err(hid_dev, "parse failed\n"); | ||
518 | goto probe_err2; | ||
519 | } | ||
520 | |||
521 | ret = hid_hw_start(hid_dev, HID_CONNECT_HIDINPUT | HID_CONNECT_HIDDEV); | ||
522 | |||
523 | if (ret) { | ||
524 | hid_err(hid_dev, "hw start failed\n"); | ||
525 | goto probe_err2; | ||
526 | } | ||
527 | |||
528 | input_dev->connected = true; | ||
529 | input_dev->init_complete = true; | ||
530 | |||
531 | return ret; | ||
532 | |||
533 | probe_err2: | ||
534 | hid_destroy_device(hid_dev); | ||
535 | |||
536 | probe_err1: | ||
537 | vmbus_close(device->channel); | ||
538 | |||
539 | probe_err0: | ||
540 | mousevsc_free_device(input_dev); | ||
541 | |||
542 | return ret; | ||
543 | } | ||
544 | |||
545 | |||
546 | static int mousevsc_remove(struct hv_device *dev) | ||
547 | { | ||
548 | struct mousevsc_dev *input_dev = hv_get_drvdata(dev); | ||
549 | |||
550 | vmbus_close(dev->channel); | ||
551 | hid_hw_stop(input_dev->hid_device); | ||
552 | hid_destroy_device(input_dev->hid_device); | ||
553 | mousevsc_free_device(input_dev); | ||
554 | |||
555 | return 0; | ||
556 | } | ||
557 | |||
558 | static const struct hv_vmbus_device_id id_table[] = { | ||
559 | /* Mouse guid */ | ||
560 | { VMBUS_DEVICE(0x9E, 0xB6, 0xA8, 0xCF, 0x4A, 0x5B, 0xc0, 0x4c, | ||
561 | 0xB9, 0x8B, 0x8B, 0xA1, 0xA1, 0xF3, 0xF9, 0x5A) }, | ||
562 | { }, | ||
563 | }; | ||
564 | |||
565 | MODULE_DEVICE_TABLE(vmbus, id_table); | ||
566 | |||
567 | static struct hv_driver mousevsc_drv = { | ||
568 | .name = KBUILD_MODNAME, | ||
569 | .id_table = id_table, | ||
570 | .probe = mousevsc_probe, | ||
571 | .remove = mousevsc_remove, | ||
572 | }; | ||
573 | |||
574 | static int __init mousevsc_init(void) | ||
575 | { | ||
576 | return vmbus_driver_register(&mousevsc_drv); | ||
577 | } | ||
578 | |||
579 | static void __exit mousevsc_exit(void) | ||
580 | { | ||
581 | vmbus_driver_unregister(&mousevsc_drv); | ||
582 | } | ||
583 | |||
584 | MODULE_LICENSE("GPL"); | ||
585 | MODULE_VERSION(HV_DRV_VERSION); | ||
586 | module_init(mousevsc_init); | ||
587 | module_exit(mousevsc_exit); | ||