aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSepherosa Ziehau <sephe@FreeBSD.org>2017-04-05 05:01:23 +0000
committerSepherosa Ziehau <sephe@FreeBSD.org>2017-04-05 05:01:23 +0000
commit3f1b91c58d8045050b99c4536e34468197d64ca3 (patch)
tree00d4c5b6a52991b7ff3e833b2408b2720b71eaa7
parentdc653882d4719cd50c1866da5aff2a862ee36fb9 (diff)
downloadsrc-3f1b91c58d8045050b99c4536e34468197d64ca3.tar.gz
src-3f1b91c58d8045050b99c4536e34468197d64ca3.zip
hyperv/kbd: Add support for synthetic keyboard.
Synthetic keyboard is the only supported keyboard on GEN2 Hyper-V. Submitted by: Hongjiang Zhang <honzhan microsoft com> MFC after: 1 week Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D10196
Notes
Notes: svn path=/head/; revision=316515
-rw-r--r--sys/conf/files.amd642
-rw-r--r--sys/dev/hyperv/input/hv_kbd.c564
-rw-r--r--sys/dev/hyperv/input/hv_kbdc.c532
-rw-r--r--sys/dev/hyperv/input/hv_kbdc.h104
4 files changed, 1202 insertions, 0 deletions
diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64
index 069f3c202760..58a7176854e0 100644
--- a/sys/conf/files.amd64
+++ b/sys/conf/files.amd64
@@ -303,6 +303,8 @@ dev/hwpmc/hwpmc_uncore.c optional hwpmc
dev/hwpmc/hwpmc_piv.c optional hwpmc
dev/hwpmc/hwpmc_tsc.c optional hwpmc
dev/hwpmc/hwpmc_x86.c optional hwpmc
+dev/hyperv/input/hv_kbd.c optional hyperv
+dev/hyperv/input/hv_kbdc.c optional hyperv
dev/hyperv/pcib/vmbus_pcib.c optional hyperv pci
dev/hyperv/netvsc/hn_nvs.c optional hyperv
dev/hyperv/netvsc/hn_rndis.c optional hyperv
diff --git a/sys/dev/hyperv/input/hv_kbd.c b/sys/dev/hyperv/input/hv_kbd.c
new file mode 100644
index 000000000000..bc9ab111afdf
--- /dev/null
+++ b/sys/dev/hyperv/input/hv_kbd.c
@@ -0,0 +1,564 @@
+/*-
+ * Copyright (c) 2017 Microsoft Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/taskqueue.h>
+#include <sys/selinfo.h>
+#include <sys/sysctl.h>
+#include <sys/poll.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/kthread.h>
+#include <sys/syscallsubr.h>
+#include <sys/sysproto.h>
+#include <sys/sema.h>
+#include <sys/signal.h>
+#include <sys/syslog.h>
+#include <sys/systm.h>
+#include <sys/mutex.h>
+#include <sys/callout.h>
+
+#include <sys/kbio.h>
+#include <dev/kbd/kbdreg.h>
+#include <dev/kbd/kbdtables.h>
+
+#include "dev/hyperv/input/hv_kbdc.h"
+
+#define HVKBD_MTX_LOCK(_m) do { \
+ mtx_lock(_m); \
+} while (0)
+
+#define HVKBD_MTX_UNLOCK(_m) do { \
+ mtx_unlock(_m); \
+} while (0)
+
+#define HVKBD_MTX_ASSERT(_m, _t) do { \
+ mtx_assert(_m, _t); \
+} while (0)
+
+#define HVKBD_LOCK() HVKBD_MTX_LOCK(&Giant)
+#define HVKBD_UNLOCK() HVKBD_MTX_UNLOCK(&Giant)
+#define HVKBD_LOCK_ASSERT() HVKBD_MTX_ASSERT(&Giant, MA_OWNED)
+
+#define HVKBD_FLAG_POLLING 0x00000002
+
+/* early keyboard probe, not supported */
+static int
+hvkbd_configure(int flags)
+{
+ return (0);
+}
+
+/* detect a keyboard, not used */
+static int
+hvkbd_probe(int unit, void *arg, int flags)
+{
+ return (ENXIO);
+}
+
+/* reset and initialize the device, not used */
+static int
+hvkbd_init(int unit, keyboard_t **kbdp, void *arg, int flags)
+{
+ DEBUG_HVKBD(*kbdp, "%s\n", __func__);
+ return (ENXIO);
+}
+
+/* test the interface to the device, not used */
+static int
+hvkbd_test_if(keyboard_t *kbd)
+{
+ DEBUG_HVKBD(kbd, "%s\n", __func__);
+ return (0);
+}
+
+/* finish using this keyboard, not used */
+static int
+hvkbd_term(keyboard_t *kbd)
+{
+ DEBUG_HVKBD(kbd, "%s\n", __func__);
+ return (ENXIO);
+}
+
+/* keyboard interrupt routine, not used */
+static int
+hvkbd_intr(keyboard_t *kbd, void *arg)
+{
+ DEBUG_HVKBD(kbd, "%s\n", __func__);
+ return (0);
+}
+
+/* lock the access to the keyboard, not used */
+static int
+hvkbd_lock(keyboard_t *kbd, int lock)
+{
+ DEBUG_HVKBD(kbd, "%s\n", __func__);
+ return (1);
+}
+
+/* save the internal state, not used */
+static int
+hvkbd_get_state(keyboard_t *kbd, void *buf, size_t len)
+{
+ DEBUG_HVKBD(kbd,"%s\n", __func__);
+ return (len == 0) ? 1 : -1;
+}
+
+/* set the internal state, not used */
+static int
+hvkbd_set_state(keyboard_t *kbd, void *buf, size_t len)
+{
+ DEBUG_HVKBD(kbd, "%s\n", __func__);
+ return (EINVAL);
+}
+
+static int
+hvkbd_poll(keyboard_t *kbd, int on)
+{
+ hv_kbd_sc *sc = kbd->kb_data;
+
+ HVKBD_LOCK();
+ /*
+ * Keep a reference count on polling to allow recursive
+ * cngrab() during a panic for example.
+ */
+ if (on)
+ sc->sc_polling++;
+ else if (sc->sc_polling > 0)
+ sc->sc_polling--;
+
+ if (sc->sc_polling != 0) {
+ sc->sc_flags |= HVKBD_FLAG_POLLING;
+ } else {
+ sc->sc_flags &= ~HVKBD_FLAG_POLLING;
+ }
+ HVKBD_UNLOCK();
+ return (0);
+}
+
+/*
+ * Enable the access to the device; until this function is called,
+ * the client cannot read from the keyboard.
+ */
+static int
+hvkbd_enable(keyboard_t *kbd)
+{
+ HVKBD_LOCK();
+ KBD_ACTIVATE(kbd);
+ HVKBD_UNLOCK();
+ return (0);
+}
+
+/* disallow the access to the device */
+static int
+hvkbd_disable(keyboard_t *kbd)
+{
+ DEBUG_HVKBD(kbd, "%s\n", __func__);
+ HVKBD_LOCK();
+ KBD_DEACTIVATE(kbd);
+ HVKBD_UNLOCK();
+ return (0);
+}
+
+static void
+hvkbd_do_poll(hv_kbd_sc *sc, uint8_t wait)
+{
+ while (!hv_kbd_prod_is_ready(sc)) {
+ hv_kbd_read_channel(NULL, sc);
+ if (!wait)
+ break;
+ }
+}
+
+/* check if data is waiting */
+/* Currently unused. */
+static int
+hvkbd_check(keyboard_t *kbd)
+{
+ DEBUG_HVKBD(kbd, "%s\n", __func__);
+ return (0);
+}
+
+/* check if char is waiting */
+static int
+hvkbd_check_char_locked(keyboard_t *kbd)
+{
+ HVKBD_LOCK_ASSERT();
+ if (!KBD_IS_ACTIVE(kbd))
+ return (FALSE);
+
+ hv_kbd_sc *sc = kbd->kb_data;
+ if (sc->sc_flags & HVKBD_FLAG_POLLING)
+ hvkbd_do_poll(sc, 0);
+ if (hv_kbd_prod_is_ready(sc)) {
+ return (TRUE);
+ }
+ return (FALSE);
+}
+
+static int
+hvkbd_check_char(keyboard_t *kbd)
+{
+ int result;
+
+ HVKBD_LOCK();
+ result = hvkbd_check_char_locked(kbd);
+ HVKBD_UNLOCK();
+
+ return (result);
+}
+
+/* read char from the keyboard */
+static uint32_t
+hvkbd_read_char_locked(keyboard_t *kbd, int wait)
+{
+ uint32_t scancode = NOKEY;
+ keystroke ks;
+ hv_kbd_sc *sc = kbd->kb_data;
+ HVKBD_LOCK_ASSERT();
+
+ if (!KBD_IS_ACTIVE(kbd) || !hv_kbd_prod_is_ready(sc))
+ return (NOKEY);
+ if (sc->sc_mode == K_RAW) {
+ if (hv_kbd_fetch_top(sc, &ks)) {
+ return (NOKEY);
+ }
+ if ((ks.info & IS_E0) || (ks.info & IS_E1)) {
+ /**
+ * Emulate the generation of E0 or E1 scancode,
+ * the real scancode will be consumed next time.
+ */
+ if (ks.info & IS_E0) {
+ scancode = XTKBD_EMUL0;
+ ks.info &= ~IS_E0;
+ } else if (ks.info & IS_E1) {
+ scancode = XTKBD_EMUL1;
+ ks.info &= ~IS_E1;
+ }
+ /**
+ * Change the top item to avoid encountering
+ * E0 or E1 twice.
+ */
+ hv_kbd_modify_top(sc, &ks);
+ } else if (ks.info & IS_UNICODE) {
+ /**
+ * XXX: Hyperv host send unicode to VM through
+ * 'Type clipboard text', the mapping from
+ * unicode to scancode depends on the keymap.
+ * It is so complicated that we do not plan to
+ * support it yet.
+ */
+ if (bootverbose)
+ device_printf(sc->dev, "Unsupported unicode\n");
+ hv_kbd_remove_top(sc);
+ return (NOKEY);
+ } else {
+ scancode = ks.makecode;
+ if (ks.info & IS_BREAK) {
+ scancode |= XTKBD_RELEASE;
+ }
+ hv_kbd_remove_top(sc);
+ }
+ } else {
+ if (bootverbose)
+ device_printf(sc->dev, "Unsupported mode: %d\n", sc->sc_mode);
+ }
+ ++kbd->kb_count;
+ DEBUG_HVKBD(kbd, "read scan: 0x%x\n", scancode);
+ return scancode;
+}
+
+/* Currently wait is always false. */
+static uint32_t
+hvkbd_read_char(keyboard_t *kbd, int wait)
+{
+ uint32_t keycode;
+
+ HVKBD_LOCK();
+ keycode = hvkbd_read_char_locked(kbd, wait);
+ HVKBD_UNLOCK();
+
+ return (keycode);
+}
+
+/* clear the internal state of the keyboard */
+static void
+hvkbd_clear_state(keyboard_t *kbd)
+{
+ hv_kbd_sc *sc = kbd->kb_data;
+ sc->sc_state &= LOCK_MASK; /* preserve locking key state */
+ sc->sc_flags &= ~HVKBD_FLAG_POLLING;
+}
+
+static int
+hvkbd_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg)
+{
+ int i;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+ defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+ int ival;
+#endif
+ hv_kbd_sc *sc = kbd->kb_data;
+ switch (cmd) {
+ case KDGKBMODE:
+ *(int *)arg = sc->sc_mode;
+ break;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+ defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+ case _IO('K', 7):
+ ival = IOCPARM_IVAL(arg);
+ arg = (caddr_t)&ival;
+ /* FALLTHROUGH */
+#endif
+ case KDSKBMODE: /* set keyboard mode */
+ DEBUG_HVKBD(kbd, "expected mode: %x\n", *(int *)arg);
+ switch (*(int *)arg) {
+ case K_XLATE:
+ if (sc->sc_mode != K_XLATE) {
+ /* make lock key state and LED state match */
+ sc->sc_state &= ~LOCK_MASK;
+ sc->sc_state |= KBD_LED_VAL(kbd);
+ }
+ /* FALLTHROUGH */
+ case K_RAW:
+ case K_CODE:
+ if (sc->sc_mode != *(int *)arg) {
+ DEBUG_HVKBD(kbd, "mod changed to %x\n", *(int *)arg);
+ if ((sc->sc_flags & HVKBD_FLAG_POLLING) == 0)
+ hvkbd_clear_state(kbd);
+ sc->sc_mode = *(int *)arg;
+ }
+ break;
+ default:
+ return (EINVAL);
+ }
+ break;
+ case KDGKBSTATE: /* get lock key state */
+ *(int *)arg = sc->sc_state & LOCK_MASK;
+ break;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+ defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+ case _IO('K', 20):
+ ival = IOCPARM_IVAL(arg);
+ arg = (caddr_t)&ival;
+ /* FALLTHROUGH */
+#endif
+ case KDSKBSTATE: /* set lock key state */
+ if (*(int *)arg & ~LOCK_MASK) {
+ return (EINVAL);
+ }
+ sc->sc_state &= ~LOCK_MASK;
+ sc->sc_state |= *(int *)arg;
+ return hvkbd_ioctl_locked(kbd, KDSETLED, arg);
+ case KDGETLED: /* get keyboard LED */
+ *(int *)arg = KBD_LED_VAL(kbd);
+ break;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+ defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+ case _IO('K', 66):
+ ival = IOCPARM_IVAL(arg);
+ arg = (caddr_t)&ival;
+ /* FALLTHROUGH */
+#endif
+ case KDSETLED: /* set keyboard LED */
+ /* NOTE: lock key state in "sc_state" won't be changed */
+ if (*(int *)arg & ~LOCK_MASK)
+ return (EINVAL);
+
+ i = *(int *)arg;
+
+ /* replace CAPS LED with ALTGR LED for ALTGR keyboards */
+ if (sc->sc_mode == K_XLATE &&
+ kbd->kb_keymap->n_keys > ALTGR_OFFSET) {
+ if (i & ALKED)
+ i |= CLKED;
+ else
+ i &= ~CLKED;
+ }
+ if (KBD_HAS_DEVICE(kbd)) {
+ DEBUG_HVSC(sc, "setled 0x%x\n", *(int *)arg);
+ }
+
+ KBD_LED_VAL(kbd) = *(int *)arg;
+ break;
+ default:
+ return (genkbd_commonioctl(kbd, cmd, arg));
+ }
+ return (0);
+}
+
+/* some useful control functions */
+static int
+hvkbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg)
+{
+ DEBUG_HVKBD(kbd, "%s: %lx start\n", __func__, cmd);
+ HVKBD_LOCK();
+ int ret = hvkbd_ioctl_locked(kbd, cmd, arg);
+ HVKBD_UNLOCK();
+ DEBUG_HVKBD(kbd, "%s: %lx end %d\n", __func__, cmd, ret);
+ return (ret);
+}
+
+/* read one byte from the keyboard if it's allowed */
+/* Currently unused. */
+static int
+hvkbd_read(keyboard_t *kbd, int wait)
+{
+ DEBUG_HVKBD(kbd, "%s\n", __func__);
+ HVKBD_LOCK_ASSERT();
+ if (!KBD_IS_ACTIVE(kbd))
+ return (-1);
+ return hvkbd_read_char_locked(kbd, wait);
+}
+
+static keyboard_switch_t hvkbdsw = {
+ hvkbd_probe, /* not used */
+ hvkbd_init,
+ hvkbd_term, /* not used */
+ hvkbd_intr, /* not used */
+ hvkbd_test_if, /* not used */
+ hvkbd_enable,
+ hvkbd_disable,
+ hvkbd_read,
+ hvkbd_check,
+ hvkbd_read_char,
+ hvkbd_check_char,
+ hvkbd_ioctl,
+ hvkbd_lock, /* not used */
+ hvkbd_clear_state,
+ hvkbd_get_state, /* not used */
+ hvkbd_set_state, /* not used */
+ genkbd_get_fkeystr,
+ hvkbd_poll,
+ genkbd_diag,
+};
+
+KEYBOARD_DRIVER(hvkbd, hvkbdsw, hvkbd_configure);
+
+void
+hv_kbd_intr(hv_kbd_sc *sc)
+{
+ uint32_t c;
+ if ((sc->sc_flags & HVKBD_FLAG_POLLING) != 0)
+ return;
+
+ if (KBD_IS_ACTIVE(&sc->sc_kbd) &&
+ KBD_IS_BUSY(&sc->sc_kbd)) {
+ /* let the callback function process the input */
+ (sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT,
+ sc->sc_kbd.kb_callback.kc_arg);
+ } else {
+ /* read and discard the input, no one is waiting for it */
+ do {
+ c = hvkbd_read_char(&sc->sc_kbd, 0);
+ } while (c != NOKEY);
+ }
+}
+
+int
+hvkbd_driver_load(module_t mod, int what, void *arg)
+{
+ switch (what) {
+ case MOD_LOAD:
+ kbd_add_driver(&hvkbd_kbd_driver);
+ break;
+ case MOD_UNLOAD:
+ kbd_delete_driver(&hvkbd_kbd_driver);
+ break;
+ }
+ return (0);
+}
+
+int
+hv_kbd_drv_attach(device_t dev)
+{
+ hv_kbd_sc *sc = device_get_softc(dev);
+ int unit = device_get_unit(dev);
+ keyboard_t *kbd = &sc->sc_kbd;
+ keyboard_switch_t *sw;
+ sw = kbd_get_switch(HVKBD_DRIVER_NAME);
+ if (sw == NULL) {
+ return (ENXIO);
+ }
+
+ kbd_init_struct(kbd, HVKBD_DRIVER_NAME, KB_OTHER, unit, 0, 0, 0);
+ kbd->kb_data = (void *)sc;
+ kbd_set_maps(kbd, &key_map, &accent_map, fkey_tab, nitems(fkey_tab));
+ KBD_FOUND_DEVICE(kbd);
+ hvkbd_clear_state(kbd);
+ KBD_PROBE_DONE(kbd);
+ KBD_INIT_DONE(kbd);
+ sc->sc_mode = K_RAW;
+ (*sw->enable)(kbd);
+
+ if (kbd_register(kbd) < 0) {
+ goto detach;
+ }
+ KBD_CONFIG_DONE(kbd);
+#ifdef KBD_INSTALL_CDEV
+ if (kbd_attach(kbd)) {
+ goto detach;
+ }
+#endif
+ if (bootverbose) {
+ genkbd_diag(kbd, bootverbose);
+ }
+ return (0);
+detach:
+ hv_kbd_drv_detach(dev);
+ return (ENXIO);
+}
+
+int
+hv_kbd_drv_detach(device_t dev)
+{
+ int error = 0;
+ hv_kbd_sc *sc = device_get_softc(dev);
+ hvkbd_disable(&sc->sc_kbd);
+ if (KBD_IS_CONFIGURED(&sc->sc_kbd)) {
+ error = kbd_unregister(&sc->sc_kbd);
+ if (error) {
+ device_printf(dev, "WARNING: kbd_unregister() "
+ "returned non-zero! (ignored)\n");
+ }
+ }
+#ifdef KBD_INSTALL_CDEV
+ error = kbd_detach(&sc->sc_kbd);
+#endif
+ return (error);
+}
+
diff --git a/sys/dev/hyperv/input/hv_kbdc.c b/sys/dev/hyperv/input/hv_kbdc.c
new file mode 100644
index 000000000000..73ede567f899
--- /dev/null
+++ b/sys/dev/hyperv/input/hv_kbdc.c
@@ -0,0 +1,532 @@
+/*-
+ * Copyright (c) 2017 Microsoft Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/taskqueue.h>
+#include <sys/selinfo.h>
+#include <sys/sysctl.h>
+#include <sys/poll.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/syscallsubr.h>
+#include <sys/sysproto.h>
+#include <sys/systm.h>
+#include <sys/mutex.h>
+
+#include <sys/kbio.h>
+#include <dev/kbd/kbdreg.h>
+#include <dev/kbd/kbdtables.h>
+
+#include <dev/hyperv/include/hyperv.h>
+#include <dev/hyperv/utilities/hv_utilreg.h>
+#include <dev/hyperv/utilities/vmbus_icreg.h>
+#include <dev/hyperv/utilities/vmbus_icvar.h>
+#include <dev/hyperv/include/vmbus_xact.h>
+
+#include "dev/hyperv/input/hv_kbdc.h"
+#include "vmbus_if.h"
+
+#define HV_KBD_VER_MAJOR (1)
+#define HV_KBD_VER_MINOR (0)
+
+#define HV_KBD_VER (HV_KBD_VER_MINOR | (HV_KBD_VER_MAJOR) << 16)
+
+#define HV_KBD_PROTO_ACCEPTED (1)
+
+#define HV_BUFF_SIZE (4*PAGE_SIZE)
+#define HV_KBD_RINGBUFF_SEND_SZ (10*PAGE_SIZE)
+#define HV_KBD_RINGBUFF_RECV_SZ (10*PAGE_SIZE)
+
+enum hv_kbd_msg_type_t {
+ HV_KBD_PROTO_REQUEST = 1,
+ HV_KBD_PROTO_RESPONSE = 2,
+ HV_KBD_PROTO_EVENT = 3,
+ HV_KBD_PROTO_LED_INDICATORS = 4,
+};
+
+typedef struct hv_kbd_msg_hdr_t {
+ uint32_t type;
+} hv_kbd_msg_hdr;
+
+typedef struct hv_kbd_msg_t {
+ hv_kbd_msg_hdr hdr;
+ char data[];
+} hv_kbd_msg;
+
+typedef struct hv_kbd_proto_req_t {
+ hv_kbd_msg_hdr hdr;
+ uint32_t ver;
+} hv_kbd_proto_req;
+
+typedef struct hv_kbd_proto_resp_t {
+ hv_kbd_msg_hdr hdr;
+ uint32_t status;
+} hv_kbd_proto_resp;
+
+#define HV_KBD_PROTO_REQ_SZ (sizeof(hv_kbd_proto_req))
+#define HV_KBD_PROTO_RESP_SZ (sizeof(hv_kbd_proto_resp))
+
+/**
+ * the struct in win host:
+ * typedef struct _HK_MESSAGE_KEYSTROKE
+ * {
+ * HK_MESSAGE_HEADER Header;
+ * UINT16 MakeCode;
+ * UINT32 IsUnicode:1;
+ * UINT32 IsBreak:1;
+ * UINT32 IsE0:1;
+ * UINT32 IsE1:1;
+ * UINT32 Reserved:28;
+ * } HK_MESSAGE_KEYSTROKE
+ */
+typedef struct hv_kbd_keystroke_t {
+ hv_kbd_msg_hdr hdr;
+ keystroke ks;
+} hv_kbd_keystroke;
+
+static const struct vmbus_ic_desc vmbus_kbd_descs[] = {
+ {
+ .ic_guid = { .hv_guid = {
+ 0x6d, 0xad, 0x12, 0xf9, 0x17, 0x2b, 0xea, 0x48,
+ 0xbd, 0x65, 0xf9, 0x27, 0xa6, 0x1c, 0x76, 0x84} },
+ .ic_desc = "Hyper-V KBD"
+ },
+ VMBUS_IC_DESC_END
+};
+
+static int hv_kbd_attach(device_t dev);
+static int hv_kbd_detach(device_t dev);
+
+/**
+ * return 1 if producer is ready
+ */
+int
+hv_kbd_prod_is_ready(hv_kbd_sc *sc)
+{
+ int ret;
+ mtx_lock(&sc->ks_mtx);
+ ret = !STAILQ_EMPTY(&sc->ks_queue);
+ mtx_unlock(&sc->ks_mtx);
+ return (ret);
+}
+
+int
+hv_kbd_produce_ks(hv_kbd_sc *sc, const keystroke *ks)
+{
+ int ret = 0;
+ keystroke_info *ksi;
+ mtx_lock(&sc->ks_mtx);
+ if (LIST_EMPTY(&sc->ks_free_list)) {
+ DEBUG_HVSC(sc, "NO buffer!\n");
+ ret = 1;
+ } else {
+ ksi = LIST_FIRST(&sc->ks_free_list);
+ LIST_REMOVE(ksi, link);
+ ksi->ks = *ks;
+ STAILQ_INSERT_TAIL(&sc->ks_queue, ksi, slink);
+ }
+ mtx_unlock(&sc->ks_mtx);
+ return (ret);
+}
+
+/**
+ * return 0 if successfully get the 1st item of queue without removing it
+ */
+int
+hv_kbd_fetch_top(hv_kbd_sc *sc, keystroke *result)
+{
+ int ret = 0;
+ keystroke_info *ksi = NULL;
+ mtx_lock(&sc->ks_mtx);
+ if (STAILQ_EMPTY(&sc->ks_queue)) {
+ DEBUG_HVSC(sc, "Empty queue!\n");
+ ret = 1;
+ } else {
+ ksi = STAILQ_FIRST(&sc->ks_queue);
+ *result = ksi->ks;
+ }
+ mtx_unlock(&sc->ks_mtx);
+ return (ret);
+}
+
+/**
+ * return 0 if successfully removing the top item
+ */
+int
+hv_kbd_remove_top(hv_kbd_sc *sc)
+{
+ int ret = 0;
+ keystroke_info *ksi = NULL;
+ mtx_lock(&sc->ks_mtx);
+ if (STAILQ_EMPTY(&sc->ks_queue)) {
+ DEBUG_HVSC(sc, "Empty queue!\n");
+ ret = 1;
+ } else {
+ ksi = STAILQ_FIRST(&sc->ks_queue);
+ STAILQ_REMOVE_HEAD(&sc->ks_queue, slink);
+ LIST_INSERT_HEAD(&sc->ks_free_list, ksi, link);
+ }
+ mtx_unlock(&sc->ks_mtx);
+ return (ret);
+}
+
+/**
+ * return 0 if successfully modify the 1st item of queue
+ */
+int
+hv_kbd_modify_top(hv_kbd_sc *sc, keystroke *top)
+{
+ int ret = 0;
+ keystroke_info *ksi = NULL;
+ mtx_lock(&sc->ks_mtx);
+ if (STAILQ_EMPTY(&sc->ks_queue)) {
+ DEBUG_HVSC(sc, "Empty queue!\n");
+ ret = 1;
+ } else {
+ ksi = STAILQ_FIRST(&sc->ks_queue);
+ ksi->ks = *top;
+ }
+ mtx_unlock(&sc->ks_mtx);
+ return (ret);
+}
+
+static int
+hv_kbd_probe(device_t dev)
+{
+ device_t bus = device_get_parent(dev);
+ const struct vmbus_ic_desc *d;
+
+ if (resource_disabled(device_get_name(dev), 0))
+ return (ENXIO);
+
+ for (d = vmbus_kbd_descs; d->ic_desc != NULL; ++d) {
+ if (VMBUS_PROBE_GUID(bus, dev, &d->ic_guid) == 0) {
+ device_set_desc(dev, d->ic_desc);
+ return (BUS_PROBE_DEFAULT);
+ }
+ }
+ return (ENXIO);
+}
+
+static void
+hv_kbd_on_response(hv_kbd_sc *sc, struct vmbus_chanpkt_hdr *pkt)
+{
+ struct vmbus_xact_ctx *xact = sc->hs_xact_ctx;
+ if (xact != NULL) {
+ DEBUG_HVSC(sc, "hvkbd is ready\n");
+ vmbus_xact_ctx_wakeup(xact, VMBUS_CHANPKT_CONST_DATA(pkt),
+ VMBUS_CHANPKT_DATALEN(pkt));
+ }
+}
+
+static void
+hv_kbd_on_received(hv_kbd_sc *sc, struct vmbus_chanpkt_hdr *pkt)
+{
+
+ const hv_kbd_msg *msg = VMBUS_CHANPKT_CONST_DATA(pkt);
+ const hv_kbd_proto_resp *resp =
+ VMBUS_CHANPKT_CONST_DATA(pkt);
+ const hv_kbd_keystroke *keystroke =
+ VMBUS_CHANPKT_CONST_DATA(pkt);
+ uint32_t msg_len = VMBUS_CHANPKT_DATALEN(pkt);
+ enum hv_kbd_msg_type_t msg_type;
+ uint32_t info;
+ uint16_t scan_code;
+
+ if (msg_len <= sizeof(hv_kbd_msg)) {
+ device_printf(sc->dev, "Illegal packet\n");
+ return;
+ }
+ msg_type = msg->hdr.type;
+ switch (msg_type) {
+ case HV_KBD_PROTO_RESPONSE:
+ hv_kbd_on_response(sc, pkt);
+ DEBUG_HVSC(sc, "keyboard resp: 0x%x\n",
+ resp->status);
+ break;
+ case HV_KBD_PROTO_EVENT:
+ info = keystroke->ks.info;
+ scan_code = keystroke->ks.makecode;
+ DEBUG_HVSC(sc, "keystroke info: 0x%x, scan: 0x%x\n",
+ info, scan_code);
+ hv_kbd_produce_ks(sc, &keystroke->ks);
+ hv_kbd_intr(sc);
+ default:
+ break;
+ }
+}
+
+void
+hv_kbd_read_channel(struct vmbus_channel *channel, void *context)
+{
+ uint8_t *buf;
+ uint32_t buflen = 0;
+ int ret = 0;
+
+ hv_kbd_sc *sc = (hv_kbd_sc*)context;
+ channel = vmbus_get_channel(sc->dev);
+ buf = sc->buf;
+ buflen = sc->buflen;
+ for (;;) {
+ struct vmbus_chanpkt_hdr *pkt = (struct vmbus_chanpkt_hdr *)buf;
+ uint32_t rxed = buflen;
+
+ ret = vmbus_chan_recv_pkt(channel, pkt, &rxed);
+ if (__predict_false(ret == ENOBUFS)) {
+ buflen = sc->buflen * 2;
+ while (buflen < rxed)
+ buflen *= 2;
+ buf = malloc(buflen, M_DEVBUF, M_WAITOK | M_ZERO);
+ device_printf(sc->dev, "expand recvbuf %d -> %d\n",
+ sc->buflen, buflen);
+ free(sc->buf, M_DEVBUF);
+ sc->buf = buf;
+ sc->buflen = buflen;
+ continue;
+ } else if (__predict_false(ret == EAGAIN)) {
+ /* No more channel packets; done! */
+ break;
+ }
+ KASSERT(!ret, ("vmbus_chan_recv_pkt failed: %d", ret));
+
+ DEBUG_HVSC(sc, "event: 0x%x\n", pkt->cph_type);
+ switch (pkt->cph_type) {
+ case VMBUS_CHANPKT_TYPE_COMP:
+ case VMBUS_CHANPKT_TYPE_RXBUF:
+ device_printf(sc->dev, "unhandled event: %d\n",
+ pkt->cph_type);
+ break;
+ case VMBUS_CHANPKT_TYPE_INBAND:
+ hv_kbd_on_received(sc, pkt);
+ break;
+ default:
+ device_printf(sc->dev, "unknown event: %d\n",
+ pkt->cph_type);
+ break;
+ }
+ }
+}
+
+static int
+hv_kbd_connect_vsp(hv_kbd_sc *sc)
+{
+ int ret;
+ size_t resplen;
+ struct vmbus_xact *xact;
+ hv_kbd_proto_req *req;
+ const hv_kbd_proto_resp *resp;
+
+ xact = vmbus_xact_get(sc->hs_xact_ctx, sizeof(*req));
+ if (xact == NULL) {
+ device_printf(sc->dev, "no xact for kbd init");
+ return (ENODEV);
+ }
+ req = vmbus_xact_req_data(xact);
+ req->hdr.type = HV_KBD_PROTO_REQUEST;
+ req->ver = HV_KBD_VER;
+
+ vmbus_xact_activate(xact);
+ ret = vmbus_chan_send(sc->hs_chan,
+ VMBUS_CHANPKT_TYPE_INBAND,
+ VMBUS_CHANPKT_FLAG_RC,
+ req, sizeof(hv_kbd_proto_req),
+ (uint64_t)(uintptr_t)xact);
+ if (ret) {
+ device_printf(sc->dev, "fail to send\n");
+ vmbus_xact_deactivate(xact);
+ return (ret);
+ }
+ resp = vmbus_chan_xact_wait(sc->hs_chan, xact, &resplen, true);
+ if (resplen < HV_KBD_PROTO_RESP_SZ) {
+ device_printf(sc->dev, "hv_kbd init communicate failed\n");
+ ret = ENODEV;
+ goto clean;
+ }
+
+ if (!(resp->status & HV_KBD_PROTO_ACCEPTED)) {
+ device_printf(sc->dev, "hv_kbd protocol request failed\n");
+ ret = ENODEV;
+ }
+clean:
+ vmbus_xact_put(xact);
+ DEBUG_HVSC(sc, "finish connect vsp\n");
+ return (ret);
+}
+
+static int
+hv_kbd_attach1(device_t dev, vmbus_chan_callback_t cb)
+{
+ int ret;
+ hv_kbd_sc *sc;
+
+ sc = device_get_softc(dev);
+ sc->buflen = HV_BUFF_SIZE;
+ sc->buf = malloc(sc->buflen, M_DEVBUF, M_WAITOK | M_ZERO);
+ vmbus_chan_set_readbatch(sc->hs_chan, false);
+ ret = vmbus_chan_open(
+ sc->hs_chan,
+ HV_KBD_RINGBUFF_SEND_SZ,
+ HV_KBD_RINGBUFF_RECV_SZ,
+ NULL, 0,
+ cb,
+ sc);
+ if (ret != 0) {
+ free(sc->buf, M_DEVBUF);
+ }
+ return (ret);
+}
+
+static int
+hv_kbd_detach1(device_t dev)
+{
+ hv_kbd_sc *sc = device_get_softc(dev);
+ vmbus_chan_close(vmbus_get_channel(dev));
+ free(sc->buf, M_DEVBUF);
+ return (0);
+}
+
+static void
+hv_kbd_init(hv_kbd_sc *sc)
+{
+ const int max_list = 16;
+ int i;
+ keystroke_info *ksi;
+
+ mtx_init(&sc->ks_mtx, "hv_kbdc mutex", NULL, MTX_DEF);
+ LIST_INIT(&sc->ks_free_list);
+ STAILQ_INIT(&sc->ks_queue);
+ for (i = 0; i < max_list; i++) {
+ ksi = malloc(sizeof(keystroke_info),
+ M_DEVBUF, M_WAITOK|M_ZERO);
+ LIST_INSERT_HEAD(&sc->ks_free_list, ksi, link);
+ }
+}
+
+static void
+hv_kbd_fini(hv_kbd_sc *sc)
+{
+ keystroke_info *ksi;
+ while (!LIST_EMPTY(&sc->ks_free_list)) {
+ ksi = LIST_FIRST(&sc->ks_free_list);
+ LIST_REMOVE(ksi, link);
+ free(ksi, M_DEVBUF);
+ }
+ while (!STAILQ_EMPTY(&sc->ks_queue)) {
+ ksi = STAILQ_FIRST(&sc->ks_queue);
+ STAILQ_REMOVE_HEAD(&sc->ks_queue, slink);
+ free(ksi, M_DEVBUF);
+ }
+ mtx_destroy(&sc->ks_mtx);
+}
+
+static void
+hv_kbd_sysctl(device_t dev)
+{
+ struct sysctl_oid_list *child;
+ struct sysctl_ctx_list *ctx;
+ hv_kbd_sc *sc;
+
+ sc = device_get_softc(dev);
+ ctx = device_get_sysctl_ctx(dev);
+ child = SYSCTL_CHILDREN(device_get_sysctl_tree(dev));
+ SYSCTL_ADD_INT(ctx, child, OID_AUTO, "debug", CTLFLAG_RW,
+ &sc->debug, 0, "debug hyperv keyboard");
+}
+
+static int
+hv_kbd_attach(device_t dev)
+{
+ int error = 0;
+ hv_kbd_sc *sc;
+
+ sc = device_get_softc(dev);
+ sc->hs_chan = vmbus_get_channel(dev);
+ sc->dev = dev;
+ hv_kbd_init(sc);
+ sc->hs_xact_ctx = vmbus_xact_ctx_create(bus_get_dma_tag(dev),
+ HV_KBD_PROTO_REQ_SZ, HV_KBD_PROTO_RESP_SZ, 0);
+ if (sc->hs_xact_ctx == NULL) {
+ error = ENOMEM;
+ goto failed;
+ }
+
+ error = hv_kbd_attach1(dev, hv_kbd_read_channel);
+ if (error)
+ goto failed;
+ error = hv_kbd_connect_vsp(sc);
+ if (error)
+ goto failed;
+
+ error = hv_kbd_drv_attach(dev);
+ if (error)
+ goto failed;
+ hv_kbd_sysctl(dev);
+ return (0);
+failed:
+ hv_kbd_detach(dev);
+ return (error);
+}
+
+static int
+hv_kbd_detach(device_t dev)
+{
+ int ret;
+ hv_kbd_sc *sc = device_get_softc(dev);
+ hv_kbd_fini(sc);
+ if (sc->hs_xact_ctx != NULL)
+ vmbus_xact_ctx_destroy(sc->hs_xact_ctx);
+ ret = hv_kbd_detach1(dev);
+ if (!ret)
+ device_printf(dev, "Fail to detach\n");
+ return hv_kbd_drv_detach(dev);
+}
+
+static device_method_t kbd_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, hv_kbd_probe),
+ DEVMETHOD(device_attach, hv_kbd_attach),
+ DEVMETHOD(device_detach, hv_kbd_detach),
+ { 0, 0 }
+};
+
+static driver_t kbd_driver = {HVKBD_DRIVER_NAME , kbd_methods, sizeof(hv_kbd_sc)};
+
+static devclass_t kbd_devclass;
+
+DRIVER_MODULE(hv_kbd, vmbus, kbd_driver, kbd_devclass, hvkbd_driver_load, NULL);
+MODULE_VERSION(hv_kbd, 1);
+MODULE_DEPEND(hv_kbd, vmbus, 1, 1, 1);
diff --git a/sys/dev/hyperv/input/hv_kbdc.h b/sys/dev/hyperv/input/hv_kbdc.h
new file mode 100644
index 000000000000..3c7bd2a69de7
--- /dev/null
+++ b/sys/dev/hyperv/input/hv_kbdc.h
@@ -0,0 +1,104 @@
+/*-
+ * Copyright (c) 2017 Microsoft Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HV_KBD_H
+#define _HV_KBD_H
+#include <sys/param.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+
+#include <dev/kbd/kbdreg.h>
+
+#define HVKBD_DRIVER_NAME "hvkbd"
+#define IS_UNICODE (1)
+#define IS_BREAK (2)
+#define IS_E0 (4)
+#define IS_E1 (8)
+
+#define XTKBD_EMUL0 (0xe0)
+#define XTKBD_EMUL1 (0xe1)
+#define XTKBD_RELEASE (0x80)
+
+#define DEBUG_HVSC(sc, ...) do { \
+ if (sc->debug > 0) { \
+ device_printf(sc->dev, __VA_ARGS__); \
+ } \
+} while (0)
+#define DEBUG_HVKBD(kbd, ...) do { \
+ hv_kbd_sc *sc = (kbd)->kb_data; \
+ DEBUG_HVSC(sc, __VA_ARGS__); \
+} while (0)
+
+struct vmbus_channel;
+struct vmbus_xact_ctx;
+
+typedef struct keystroke_t {
+ uint16_t makecode;
+ uint32_t info;
+} keystroke;
+
+typedef struct keystroke_info {
+ LIST_ENTRY(keystroke_info) link;
+ STAILQ_ENTRY(keystroke_info) slink;
+ keystroke ks;
+} keystroke_info;
+
+typedef struct hv_kbd_sc_t {
+ struct vmbus_channel *hs_chan;
+ device_t dev;
+ struct vmbus_xact_ctx *hs_xact_ctx;
+ int32_t buflen;
+ uint8_t *buf;
+
+ struct mtx ks_mtx;
+ LIST_HEAD(, keystroke_info) ks_free_list;
+ STAILQ_HEAD(, keystroke_info) ks_queue; /* keystroke info queue */
+
+ keyboard_t sc_kbd;
+ int sc_mode;
+ int sc_state;
+ int sc_polling; /* polling recursion count */
+ uint32_t sc_flags;
+ int debug;
+} hv_kbd_sc;
+
+int hv_kbd_produce_ks(hv_kbd_sc *sc, const keystroke *ks);
+int hv_kbd_fetch_top(hv_kbd_sc *sc, keystroke *top);
+int hv_kbd_modify_top(hv_kbd_sc *sc, keystroke *top);
+int hv_kbd_remove_top(hv_kbd_sc *sc);
+int hv_kbd_prod_is_ready(hv_kbd_sc *sc);
+void hv_kbd_read_channel(struct vmbus_channel *, void *);
+
+int hv_kbd_drv_attach(device_t dev);
+int hv_kbd_drv_detach(device_t dev);
+
+int hvkbd_driver_load(module_t, int, void *);
+void hv_kbd_intr(hv_kbd_sc *sc);
+#endif