Index: sys/conf/files.amd64 =================================================================== --- sys/conf/files.amd64 +++ sys/conf/files.amd64 @@ -303,6 +303,8 @@ 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 Index: sys/dev/hyperv/input/hv_kbd.c =================================================================== --- /dev/null +++ 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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); +} + Index: sys/dev/hyperv/input/hv_kbdc.h =================================================================== --- /dev/null +++ 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 +#include +#include +#include +#include + +#include + +#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 Index: sys/dev/hyperv/input/hv_kbdc.c =================================================================== --- /dev/null +++ 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#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);