hvkbd.c revision 1.2
1/* $NetBSD: hvkbd.c,v 1.2 2019/07/21 16:08:13 rin Exp $ */ 2 3/*- 4 * Copyright (c) 2017 Microsoft Corp. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice unmodified, this list of conditions, and the following 12 * disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 * 28 * $FreeBSD: head/sys/dev/hyperv/input/hv_kbd.c 317821 2017-05-05 03:28:30Z sephe $ 29 * $FreeBSD: head/sys/dev/hyperv/input/hv_kbdc.c 320490 2017-06-30 03:01:22Z sephe $ 30 * $FreeBSD: head/sys/dev/hyperv/input/hv_kbdc.h 316515 2017-04-05 05:01:23Z sephe $ 31 */ 32 33#ifdef _KERNEL_OPT 34#include "opt_pckbd_layout.h" 35#include "opt_wsdisplay_compat.h" 36#endif /* _KERNEL_OPT */ 37 38#include <sys/cdefs.h> 39__KERNEL_RCSID(0, "$NetBSD: hvkbd.c,v 1.2 2019/07/21 16:08:13 rin Exp $"); 40 41#include <sys/param.h> 42#include <sys/systm.h> 43#include <sys/device.h> 44#include <sys/mutex.h> 45#include <sys/kernel.h> 46#include <sys/kmem.h> 47#include <sys/module.h> 48#include <sys/pmf.h> 49#include <sys/proc.h> 50#include <sys/queue.h> 51 52#include <dev/hyperv/vmbusvar.h> 53#include <dev/hyperv/hvkbdvar.h> 54 55#include <dev/wscons/wsconsio.h> 56#include <dev/wscons/wskbdvar.h> 57#include <dev/wscons/wsksymdef.h> 58#include <dev/wscons/wsksymvar.h> 59#include <dev/pckbport/wskbdmap_mfii.h> 60 61#define HVKBD_BUFSIZE (4 * PAGE_SIZE) 62#define HVKBD_TX_RING_SIZE (10 * PAGE_SIZE) 63#define HVKBD_RX_RING_SIZE (10 * PAGE_SIZE) 64 65#define HVKBD_VER_MAJOR (1) 66#define HVKBD_VER_MINOR (0) 67#define HVKBD_VERSION ((HVKBD_VER_MAJOR << 16) | HVKBD_VER_MINOR) 68 69enum hvkbd_msg_type { 70 HVKBD_PROTO_REQUEST = 1, 71 HVKBD_PROTO_RESPONSE = 2, 72 HVKBD_PROTO_EVENT = 3, 73 HVKBD_PROTO_LED_INDICATORS = 4 74}; 75 76struct hvkbd_msg_hdr { 77 uint32_t type; 78} __packed; 79 80struct hvkbd_proto_req { 81 struct hvkbd_msg_hdr hdr; 82 uint32_t ver; 83} __packed; 84 85struct hvkbd_proto_resp { 86 struct hvkbd_msg_hdr hdr; 87 uint32_t status; 88#define RESP_STATUS_ACCEPTED __BIT(0) 89} __packed; 90 91struct keystroke { 92 uint16_t makecode; 93 uint16_t pad0; 94 uint32_t info; 95#define KS_INFO_UNICODE __BIT(0) 96#define KS_INFO_BREAK __BIT(1) 97#define KS_INFO_E0 __BIT(2) 98#define KS_INFO_E1 __BIT(3) 99} __packed; 100 101struct hvkbd_keystroke { 102 struct hvkbd_msg_hdr hdr; 103 struct keystroke ks; 104} __packed; 105 106struct hvkbd_keystroke_info { 107 LIST_ENTRY(hvkbd_keystroke_info) link; 108 STAILQ_ENTRY(hvkbd_keystroke_info) slink; 109 struct keystroke ks; 110}; 111 112#define HVKBD_KEYBUF_SIZE 16 113 114struct hvkbd_softc { 115 device_t sc_dev; 116 117 struct vmbus_channel *sc_chan; 118 void *sc_buf; 119 120 kmutex_t sc_ks_lock; 121 LIST_HEAD(, hvkbd_keystroke_info) sc_ks_free; 122 STAILQ_HEAD(, hvkbd_keystroke_info) sc_ks_queue; 123 124 int sc_enabled; 125 int sc_polling; 126 int sc_console_keyboard; 127#if defined(WSDISPLAY_COMPAT_RAWKBD) 128 int sc_rawkbd; 129#endif 130 131 int sc_connected; 132 uint32_t sc_connect_status; 133 134 device_t sc_wskbddev; 135}; 136 137static int hvkbd_match(device_t, cfdata_t, void *); 138static void hvkbd_attach(device_t, device_t, void *); 139 140CFATTACH_DECL_NEW(hvkbd, sizeof(struct hvkbd_softc), 141 hvkbd_match, hvkbd_attach, NULL, NULL); 142 143static int hvkbd_alloc_keybuf(struct hvkbd_softc *); 144static void hvkbd_free_keybuf(struct hvkbd_softc *); 145 146static int hvkbd_enable(void *, int); 147static void hvkbd_set_leds(void *, int); 148static int hvkbd_ioctl(void *, u_long, void *, int, struct lwp *); 149 150static const struct wskbd_accessops hvkbd_accessops = { 151 hvkbd_enable, 152 hvkbd_set_leds, 153 hvkbd_ioctl, 154}; 155 156static const struct wskbd_mapdata hvkbd_keymapdata = { 157 pckbd_keydesctab, 158#if defined(PCKBD_LAYOUT) 159 PCKBD_LAYOUT, 160#else 161 KB_US, 162#endif 163}; 164 165static int hvkbd_connect(struct hvkbd_softc *); 166static void hvkbd_intr(void *); 167 168static void hvkbd_cngetc(void *, u_int *, int *); 169static void hvkbd_cnpollc(void *, int); 170 171static const struct wskbd_consops hvkbd_consops = { 172 .getc = hvkbd_cngetc, 173 .pollc = hvkbd_cnpollc, 174 .bell = NULL, 175}; 176 177static int hvkbd_is_console; 178 179static int 180hvkbd_match(device_t parent, cfdata_t cf, void *aux) 181{ 182 struct vmbus_attach_args *aa = aux; 183 184 if (memcmp(aa->aa_type, &hyperv_guid_kbd, sizeof(*aa->aa_type)) != 0) 185 return 0; 186 187 /* If hvkbd(4) is not console, we use pckbd(4) in Gen.1 VM. */ 188 if (!hvkbd_is_console && hyperv_is_gen1()) 189 return 0; 190 191 return 1; 192} 193 194static void 195hvkbd_attach(device_t parent, device_t self, void *aux) 196{ 197 struct hvkbd_softc *sc = device_private(self); 198 struct vmbus_attach_args *aa = aux; 199 struct wskbddev_attach_args a; 200 201 sc->sc_dev = self; 202 sc->sc_chan = aa->aa_chan; 203 204 aprint_naive("\n"); 205 aprint_normal(": Hyper-V Synthetic Keyboard\n"); 206 207 mutex_init(&sc->sc_ks_lock, MUTEX_DEFAULT, IPL_TTY); 208 LIST_INIT(&sc->sc_ks_free); 209 STAILQ_INIT(&sc->sc_ks_queue); 210 hvkbd_alloc_keybuf(sc); 211 212 sc->sc_buf = kmem_zalloc(HVKBD_BUFSIZE, cold ? KM_NOSLEEP : KM_SLEEP); 213 if (sc->sc_buf == NULL) { 214 aprint_error_dev(self, 215 "failed to allocate channel data buffer\n"); 216 return; 217 } 218 219 if (vmbus_channel_setdeferred(sc->sc_chan, device_xname(self))) { 220 aprint_error_dev(self, 221 "failed to create the interrupt thread\n"); 222 goto free_buf; 223 } 224 225 sc->sc_chan->ch_flags &= ~CHF_BATCHED; 226 if (vmbus_channel_open(sc->sc_chan, 227 HVKBD_TX_RING_SIZE + HVKBD_RX_RING_SIZE, NULL, 0, hvkbd_intr, sc)) { 228 aprint_error_dev(self, "failed to open channel\n"); 229 goto free_buf; 230 } 231 232 if (hvkbd_connect(sc)) 233 goto free_buf; 234 235 if (!pmf_device_register(self, NULL, NULL)) 236 aprint_error_dev(self, "couldn't establish power handler\n"); 237 238 sc->sc_console_keyboard = hvkbd_is_console; 239 if (hvkbd_is_console) 240 hvkbd_is_console = 0; 241 242 if (sc->sc_console_keyboard) { 243 wskbd_cnattach(&hvkbd_consops, sc, &hvkbd_keymapdata); 244 hvkbd_enable(sc, 1); 245 } 246 247 a.console = sc->sc_console_keyboard; 248 a.keymap = &hvkbd_keymapdata; 249 a.accessops = &hvkbd_accessops; 250 a.accesscookie = sc; 251 sc->sc_wskbddev = config_found(self, &a, wskbddevprint); 252 return; 253 254free_buf: 255 if (sc->sc_buf != NULL) { 256 kmem_free(sc->sc_buf, HVKBD_BUFSIZE); 257 sc->sc_buf = NULL; 258 } 259 hvkbd_free_keybuf(sc); 260} 261 262static int 263hvkbd_alloc_keybuf(struct hvkbd_softc *sc) 264{ 265 struct hvkbd_keystroke_info *ksi; 266 int i; 267 268 for (i = 0; i < HVKBD_KEYBUF_SIZE; i++) { 269 ksi = kmem_zalloc(sizeof(*ksi), cold ? KM_NOSLEEP : KM_SLEEP); 270 if (ksi != NULL) { 271 LIST_INSERT_HEAD(&sc->sc_ks_free, ksi, link); 272 continue; 273 } 274 275 while ((ksi = LIST_FIRST(&sc->sc_ks_free)) != NULL) { 276 LIST_REMOVE(ksi, link); 277 kmem_free(ksi, sizeof(*ksi)); 278 } 279 return ENOMEM; 280 281 } 282 283 return 0; 284} 285 286static void 287hvkbd_free_keybuf(struct hvkbd_softc *sc) 288{ 289 struct hvkbd_keystroke_info *ksi; 290 291 while ((ksi = STAILQ_FIRST(&sc->sc_ks_queue)) != NULL) { 292 STAILQ_REMOVE(&sc->sc_ks_queue, ksi, hvkbd_keystroke_info, 293 slink); 294 kmem_free(ksi, sizeof(*ksi)); 295 } 296 while ((ksi = LIST_FIRST(&sc->sc_ks_free)) != NULL) { 297 LIST_REMOVE(ksi, link); 298 kmem_free(ksi, sizeof(*ksi)); 299 } 300} 301 302int 303hvkbd_enable(void *v, int on) 304{ 305 struct hvkbd_softc *sc = v; 306 307 sc->sc_enabled = on; 308 309 return 0; 310} 311 312static void 313hvkbd_set_leds(void *v, int leds) 314{ 315} 316 317static int 318hvkbd_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l) 319{ 320#if defined(WSDISPLAY_COMPAT_RAWKBD) 321 struct hvkbd_softc *sc = v; 322#endif 323 324 switch (cmd) { 325 case WSKBDIO_GTYPE: 326 *(int *)data = WSKBD_TYPE_HYPERV; 327 return 0; 328 329 case WSKBDIO_SETLEDS: 330 hvkbd_set_leds(v, *(int *)data); 331 return 0; 332 333 case WSKBDIO_GETLEDS: 334 *(int *)data = 0; 335 return 0; 336 337#if defined(WSDISPLAY_COMPAT_RAWKBD) 338 case WSKBDIO_SETMODE: 339 sc->sc_rawkbd = (*(int *)data == WSKBD_RAW); 340 return 0; 341#endif 342 } 343 344 return EPASSTHROUGH; 345} 346 347static int 348hvkbd_connect(struct hvkbd_softc *sc) 349{ 350 struct hvkbd_proto_req req; 351 int timo = 100; 352 int error, s; 353 354 sc->sc_connected = 0; 355 356 memset(&req, 0, sizeof(req)); 357 req.hdr.type = HVKBD_PROTO_REQUEST; 358 req.ver = HVKBD_VERSION; 359 error = vmbus_channel_send(sc->sc_chan, &req, sizeof(req), 360 0, VMBUS_CHANPKT_TYPE_INBAND, VMBUS_CHANPKT_FLAG_RC); 361 if (error) { 362 aprint_error_dev(sc->sc_dev, "failed to send connect: %d\n", 363 error); 364 return error; 365 } 366 367 do { 368 if (cold) 369 delay(1000); 370 else 371 tsleep(sc, PRIBIO | PCATCH, "hvkbdcon", 1); 372 s = spltty(); 373 hvkbd_intr(sc); 374 splx(s); 375 } while (--timo > 0 && sc->sc_connected == 0); 376 377 if (timo == 0 && sc->sc_connected == 0) { 378 aprint_error_dev(sc->sc_dev, "connect timed out\n"); 379 return ETIMEDOUT; 380 } 381 382 if (!(sc->sc_connect_status & RESP_STATUS_ACCEPTED)) { 383 aprint_error_dev(sc->sc_dev, "protocol request failed\n"); 384 return ENODEV; 385 } 386 387 return 0; 388} 389 390static int 391hvkbd_keybuf_add_keystroke(struct hvkbd_softc *sc, const struct keystroke *ks) 392{ 393 struct hvkbd_keystroke_info *ksi; 394 395 mutex_enter(&sc->sc_ks_lock); 396 ksi = LIST_FIRST(&sc->sc_ks_free); 397 if (ksi != NULL) { 398 LIST_REMOVE(ksi, link); 399 ksi->ks = *ks; 400 STAILQ_INSERT_TAIL(&sc->sc_ks_queue, ksi, slink); 401 } 402 mutex_exit(&sc->sc_ks_lock); 403 404 return (ksi != NULL) ? 0 : 1; 405} 406 407static int 408hvkbd_decode(struct hvkbd_softc *sc, u_int *type, int *scancode) 409{ 410 struct hvkbd_keystroke_info *ksi; 411 struct keystroke ks; 412 413 mutex_enter(&sc->sc_ks_lock); 414 ksi = STAILQ_FIRST(&sc->sc_ks_queue); 415 if (ksi != NULL) { 416 STAILQ_REMOVE_HEAD(&sc->sc_ks_queue, slink); 417 ks = ksi->ks; 418 LIST_INSERT_HEAD(&sc->sc_ks_free, ksi, link); 419 } 420 mutex_exit(&sc->sc_ks_lock); 421 422 if (ksi == NULL) 423 return 0; 424 425 /* 426 * XXX: Hyper-V host send unicode to VM through 'Type clipboard text', 427 * the mapping from unicode to scancode depends on the keymap. 428 * It is so complicated that we do not plan to support it yet. 429 */ 430 if (ks.info & KS_INFO_UNICODE) 431 return 0; 432 433 *type = (ks.info & KS_INFO_BREAK) ? 434 WSCONS_EVENT_KEY_UP : WSCONS_EVENT_KEY_DOWN; 435 *scancode = ks.makecode; 436 return 1; 437} 438 439#if defined(WSDISPLAY_COMPAT_RAWKBD) 440static int 441hvkbd_encode(struct hvkbd_softc *sc, u_char *buf, int *len) 442{ 443 struct hvkbd_keystroke_info *ksi; 444 struct keystroke ks; 445 int i; 446 447 mutex_enter(&sc->sc_ks_lock); 448 ksi = STAILQ_FIRST(&sc->sc_ks_queue); 449 if (ksi != NULL) { 450 STAILQ_REMOVE_HEAD(&sc->sc_ks_queue, slink); 451 ks = ksi->ks; 452 LIST_INSERT_HEAD(&sc->sc_ks_free, ksi, link); 453 } 454 mutex_exit(&sc->sc_ks_lock); 455 456 if (ksi == NULL) 457 return 0; 458 459 /* 460 * XXX: Hyper-V host send unicode to VM through 'Type clipboard text', 461 * the mapping from unicode to scancode depends on the keymap. 462 * It is so complicated that we do not plan to support it yet. 463 */ 464 if (ks.info & KS_INFO_UNICODE) 465 return 0; 466 467 i = 0; 468 if (ks.info & (KS_INFO_E0|KS_INFO_E1)) { 469 if (ks.info & KS_INFO_E0) 470 buf[i++] = 0xe0; 471 else 472 buf[i++] = 0xe1; 473 } 474 if (ks.info & KS_INFO_BREAK) 475 buf[i++] = (u_char)ks.makecode & 0x80; 476 else 477 buf[i++] = (u_char)ks.makecode; 478 479 KDASSERT(i <= *len); 480 *len = i; 481 482 return 1; 483} 484#endif 485 486static void 487hvkbd_intr(void *xsc) 488{ 489 struct hvkbd_softc *sc = xsc; 490 struct vmbus_chanpkt_hdr *cph; 491 const struct hvkbd_msg_hdr *hdr; 492 const struct hvkbd_proto_resp *rsp; 493 const struct hvkbd_keystroke *ks; 494 uint64_t rid; 495 uint32_t rlen; 496 u_int type; 497 int key, error; 498 499 for (;;) { 500 error = vmbus_channel_recv(sc->sc_chan, sc->sc_buf, 501 HVKBD_BUFSIZE, &rlen, &rid, 1); 502 if (error != 0 || rlen == 0) { 503 if (error != EAGAIN) 504 device_printf(sc->sc_dev, 505 "failed to receive a reply packet\n"); 506 return; 507 } 508 509 cph = (struct vmbus_chanpkt_hdr *)sc->sc_buf; 510 switch (cph->cph_type) { 511 case VMBUS_CHANPKT_TYPE_INBAND: 512 hdr = VMBUS_CHANPKT_CONST_DATA(cph); 513 if (rlen < sizeof(*hdr)) { 514 device_printf(sc->sc_dev, "Illegal packet\n"); 515 continue; 516 } 517 518 switch (hdr->type) { 519 case HVKBD_PROTO_RESPONSE: 520 if (!sc->sc_connected) { 521 rsp = VMBUS_CHANPKT_CONST_DATA(cph); 522 if (rlen < sizeof(*rsp)) { 523 device_printf(sc->sc_dev, 524 "Illegal resp packet\n"); 525 break; 526 } 527 sc->sc_connect_status = rsp->status; 528 sc->sc_connected = 1; 529 wakeup(sc); 530 } 531 break; 532 533 case HVKBD_PROTO_EVENT: 534 if (sc->sc_wskbddev == NULL || !sc->sc_enabled) 535 break; 536 537 ks = VMBUS_CHANPKT_CONST_DATA(cph); 538 hvkbd_keybuf_add_keystroke(sc, &ks->ks); 539 if (sc->sc_polling) 540 break; 541 542#if defined(WSDISPLAY_COMPAT_RAWKBD) 543 if (sc->sc_rawkbd) { 544 u_char buf[2]; 545 int len; 546 547 len = sizeof(buf); 548 if (hvkbd_encode(sc, buf, &len)) { 549 wskbd_rawinput(sc->sc_wskbddev, 550 buf, len); 551 } 552 break; 553 } 554#endif 555 if (hvkbd_decode(sc, &type, &key)) 556 wskbd_input(sc->sc_wskbddev, type, key); 557 break; 558 559 case HVKBD_PROTO_REQUEST: 560 case HVKBD_PROTO_LED_INDICATORS: 561 device_printf(sc->sc_dev, 562 "unhandled message: %d\n", hdr->type); 563 break; 564 565 default: 566 device_printf(sc->sc_dev, 567 "unknown message: %d\n", hdr->type); 568 break; 569 } 570 break; 571 572 case VMBUS_CHANPKT_TYPE_COMP: 573 case VMBUS_CHANPKT_TYPE_RXBUF: 574 device_printf(sc->sc_dev, "unhandled event: %d\n", 575 cph->cph_type); 576 break; 577 578 default: 579 device_printf(sc->sc_dev, "unknown event: %d\n", 580 cph->cph_type); 581 break; 582 } 583 } 584} 585 586int 587hvkbd_cnattach(void) 588{ 589 590 hvkbd_is_console = 1; 591 592 return 0; 593} 594 595static void 596hvkbd_cngetc(void *v, u_int *type, int *data) 597{ 598 struct hvkbd_softc *sc = v; 599 600 while (!hvkbd_decode(sc, type, data)) 601 hvkbd_intr(sc); 602} 603 604static void 605hvkbd_cnpollc(void *v, int on) 606{ 607 struct hvkbd_softc *sc = v; 608 609 sc->sc_polling = on; 610} 611 612MODULE(MODULE_CLASS_DRIVER, hvkbd, "vmbus"); 613 614#ifdef _MODULE 615#include "ioconf.c" 616#endif 617 618static int 619hvkbd_modcmd(modcmd_t cmd, void *aux) 620{ 621 int error = 0; 622 623 switch (cmd) { 624 case MODULE_CMD_INIT: 625#ifdef _MODULE 626 error = config_init_component(cfdriver_ioconf_hvkbd, 627 cfattach_ioconf_hvkbd, cfdata_ioconf_hvkbd); 628#endif 629 break; 630 631 case MODULE_CMD_FINI: 632#ifdef _MODULE 633 error = config_fini_component(cfdriver_ioconf_hvtkbd, 634 cfattach_ioconf_hvkbd, cfdata_ioconf_hvkbd); 635#endif 636 break; 637 638 case MODULE_CMD_AUTOUNLOAD: 639 error = EBUSY; 640 break; 641 642 default: 643 error = ENOTTY; 644 break; 645 } 646 647 return error; 648} 649