Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/hyperv/input/hv_kbd.c
- This file was added.
/*- | |||||
* 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 { | |||||
scancode = ks.makecode; | |||||
if (ks.info & IS_BREAK) { | |||||
scancode |= XTKBD_RELEASE; | |||||
} | |||||
hv_kbd_remove_top(sc); | |||||
} | |||||
} else { | |||||
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); | |||||
} | |||||