#include <sys/param.h>
#include <sys/systm.h>
#include <sys/timeout.h>
#include <sys/device.h>
#include <machine/bus.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usbdevs.h>
#include <dev/usb/usb_quirks.h>
#include <dev/usb/uhidev.h>
#include <dev/usb/ukbdvar.h>
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wskbdvar.h>
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wsksymvar.h>
#include <dev/hid/hidkbdsc.h>
#ifdef UKBD_DEBUG
#define DPRINTF(x) do { if (ukbddebug) printf x; } while (0)
#define DPRINTFN(n,x) do { if (ukbddebug>(n)) printf x; } while (0)
int ukbddebug = 0;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif
const kbd_t ukbd_countrylayout[1 + HCC_MAX] = {
(kbd_t)-1,
(kbd_t)-1,
KB_BE,
(kbd_t)-1,
KB_CF,
(kbd_t)-1,
KB_DK,
(kbd_t)-1,
KB_FR,
KB_DE,
(kbd_t)-1,
(kbd_t)-1,
KB_HU,
(kbd_t)-1,
KB_IT,
KB_JP,
(kbd_t)-1,
KB_LA,
(kbd_t)-1,
KB_NO,
(kbd_t)-1,
KB_PL,
KB_PT,
KB_RU,
(kbd_t)-1,
KB_ES,
KB_SV,
KB_SF,
KB_SG,
(kbd_t)-1,
(kbd_t)-1,
KB_TR,
KB_UK,
KB_US,
(kbd_t)-1,
(kbd_t)-1
};
struct ukbd_softc {
struct uhidev sc_hdev;
#define sc_ledsize sc_hdev.sc_osize
struct hidkbd sc_kbd;
int sc_spl;
#ifdef DDB
struct timeout sc_ddb;
#endif
};
void ukbd_cngetc(void *, u_int *, int *);
void ukbd_cnpollc(void *, int);
void ukbd_cnbell(void *, u_int, u_int, u_int);
void ukbd_debugger(void *);
const struct wskbd_consops ukbd_consops = {
ukbd_cngetc,
ukbd_cnpollc,
ukbd_cnbell,
#ifdef DDB
ukbd_debugger,
#endif
};
void ukbd_intr(struct uhidev *addr, void *ibuf, u_int len);
void ukbd_db_enter(void *);
int ukbd_enable(void *, int);
void ukbd_set_leds(void *, int);
int ukbd_ioctl(void *, u_long, caddr_t, int, struct proc *);
const struct wskbd_accessops ukbd_accessops = {
ukbd_enable,
ukbd_set_leds,
ukbd_ioctl,
};
int ukbd_match(struct device *, void *, void *);
void ukbd_attach(struct device *, struct device *, void *);
int ukbd_detach(struct device *, int);
struct cfdriver ukbd_cd = {
NULL, "ukbd", DV_DULL
};
const struct cfattach ukbd_ca = {
sizeof(struct ukbd_softc), ukbd_match, ukbd_attach, ukbd_detach
};
#ifdef __loongson__
void ukbd_gdium_munge(void *, uint8_t *, u_int);
#endif
const struct usb_devno ukbd_never_console[] = {
{ USB_VENDOR_APPLE, USB_PRODUCT_APPLE_BLUETOOTH_HCI },
{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPER },
{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPERHUM },
{ USB_VENDOR_PCSENSORS, USB_PRODUCT_PCSENSORS_TEMPER },
{ USB_VENDOR_RDING, USB_PRODUCT_RDING_TEMPER },
{ USB_VENDOR_WCH2, USB_PRODUCT_WCH2_TEMPER },
};
int
ukbd_match(struct device *parent, void *match, void *aux)
{
struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
int size;
void *desc;
if (uha->uaa->vendor == USB_VENDOR_YUBICO)
return (UMATCH_NONE);
if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
return (UMATCH_NONE);
uhidev_get_report_desc(uha->parent, &desc, &size);
if (!hid_is_collection(desc, size, uha->reportid,
HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
return (UMATCH_NONE);
return (UMATCH_IFACECLASS);
}
void
ukbd_attach(struct device *parent, struct device *self, void *aux)
{
struct ukbd_softc *sc = (struct ukbd_softc *)self;
struct hidkbd *kbd = &sc->sc_kbd;
struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
struct usb_hid_descriptor *hid;
u_int32_t quirks, qflags = 0;
int dlen, repid;
int console = 1;
void *desc;
kbd_t layout = (kbd_t)-1;
sc->sc_hdev.sc_intr = ukbd_intr;
sc->sc_hdev.sc_parent = uha->parent;
sc->sc_hdev.sc_udev = uha->uaa->device;
sc->sc_hdev.sc_report_id = uha->reportid;
usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0);
uhidev_get_report_desc(uha->parent, &desc, &dlen);
repid = uha->reportid;
sc->sc_hdev.sc_isize = hid_report_size(desc, dlen, hid_input, repid);
sc->sc_hdev.sc_osize = hid_report_size(desc, dlen, hid_output, repid);
sc->sc_hdev.sc_fsize = hid_report_size(desc, dlen, hid_feature, repid);
if (usb_lookup(ukbd_never_console, uha->uaa->vendor, uha->uaa->product))
console = 0;
quirks = usbd_get_quirks(sc->sc_hdev.sc_udev)->uq_flags;
if (quirks & UQ_SPUR_BUT_UP)
qflags |= HIDKBD_SPUR_BUT_UP;
if (hidkbd_attach(self, kbd, console, qflags, repid, desc, dlen) != 0)
return;
if (uha->uaa->vendor == USB_VENDOR_APPLE) {
if (hid_locate(desc, dlen, HID_USAGE2(HUP_APPLE, HUG_FN_KEY),
uha->reportid, hid_input, &kbd->sc_fn, &qflags)) {
if (qflags & HIO_VARIABLE) {
switch (uha->uaa->product) {
case USB_PRODUCT_APPLE_FOUNTAIN_ISO:
case USB_PRODUCT_APPLE_GEYSER_ISO:
case USB_PRODUCT_APPLE_GEYSER3_ISO:
case USB_PRODUCT_APPLE_WELLSPRING6_ISO:
case USB_PRODUCT_APPLE_WELLSPRING8_ISO:
kbd->sc_munge = hidkbd_apple_iso_munge;
break;
case USB_PRODUCT_APPLE_WELLSPRING_ISO:
case USB_PRODUCT_APPLE_WELLSPRING4_ISO:
case USB_PRODUCT_APPLE_WELLSPRING4A_ISO:
kbd->sc_munge = hidkbd_apple_iso_mba_munge;
break;
case USB_PRODUCT_APPLE_WELLSPRING_ANSI:
case USB_PRODUCT_APPLE_WELLSPRING_JIS:
case USB_PRODUCT_APPLE_WELLSPRING4_ANSI:
case USB_PRODUCT_APPLE_WELLSPRING4_JIS:
case USB_PRODUCT_APPLE_WELLSPRING4A_ANSI:
case USB_PRODUCT_APPLE_WELLSPRING4A_JIS:
kbd->sc_munge = hidkbd_apple_mba_munge;
break;
default:
kbd->sc_munge = hidkbd_apple_munge;
break;
}
}
}
}
if (uha->uaa->vendor == USB_VENDOR_TOPRE &&
uha->uaa->product == USB_PRODUCT_TOPRE_HHKB) {
} else {
usb_interface_descriptor_t *id;
id = usbd_get_interface_descriptor(uha->uaa->iface);
hid = usbd_get_hid_descriptor(uha->uaa->device, id);
if (hid->bCountryCode <= HCC_MAX)
layout = ukbd_countrylayout[hid->bCountryCode];
#ifdef DIAGNOSTIC
if (hid->bCountryCode != 0)
printf(", country code %d", hid->bCountryCode);
#endif
}
if (layout == (kbd_t)-1) {
#ifdef UKBD_LAYOUT
layout = UKBD_LAYOUT;
#else
layout = KB_US | KB_DEFAULT;
#endif
}
printf("\n");
#ifdef __loongson__
if (uha->uaa->vendor == USB_VENDOR_CYPRESS &&
uha->uaa->product == USB_PRODUCT_CYPRESS_LPRDK)
kbd->sc_munge = ukbd_gdium_munge;
#endif
if (kbd->sc_console_keyboard) {
extern struct wskbd_mapdata ukbd_keymapdata;
DPRINTF(("ukbd_attach: console keyboard sc=%p\n", sc));
ukbd_keymapdata.layout = layout;
wskbd_cnattach(&ukbd_consops, sc, &ukbd_keymapdata);
ukbd_enable(sc, 1);
}
ukbd_set_leds(sc, WSKBD_LED_SCROLL | WSKBD_LED_NUM |
WSKBD_LED_CAPS | WSKBD_LED_COMPOSE);
usbd_delay_ms(sc->sc_hdev.sc_udev, 400);
ukbd_set_leds(sc, 0);
hidkbd_attach_wskbd(kbd, layout, &ukbd_accessops);
#ifdef DDB
timeout_set(&sc->sc_ddb, ukbd_db_enter, sc);
#endif
}
int
ukbd_detach(struct device *self, int flags)
{
struct ukbd_softc *sc = (struct ukbd_softc *)self;
struct hidkbd *kbd = &sc->sc_kbd;
int rv;
rv = hidkbd_detach(kbd, flags);
if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
uhidev_close(&sc->sc_hdev);
return (rv);
}
void
ukbd_intr(struct uhidev *addr, void *ibuf, u_int len)
{
struct ukbd_softc *sc = (struct ukbd_softc *)addr;
struct hidkbd *kbd = &sc->sc_kbd;
if (kbd->sc_enabled != 0)
hidkbd_input(kbd, (uint8_t *)ibuf, len);
}
int
ukbd_enable(void *v, int on)
{
struct ukbd_softc *sc = v;
struct hidkbd *kbd = &sc->sc_kbd;
int rv;
if (on && usbd_is_dying(sc->sc_hdev.sc_udev))
return EIO;
if ((rv = hidkbd_enable(kbd, on)) != 0)
return rv;
if (on) {
return uhidev_open(&sc->sc_hdev);
} else {
uhidev_close(&sc->sc_hdev);
return 0;
}
}
void
ukbd_set_leds(void *v, int leds)
{
struct ukbd_softc *sc = v;
struct hidkbd *kbd = &sc->sc_kbd;
u_int8_t res;
if (usbd_is_dying(sc->sc_hdev.sc_udev))
return;
if (sc->sc_ledsize && hidkbd_set_leds(kbd, leds, &res) != 0)
uhidev_set_report_async(sc->sc_hdev.sc_parent,
UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, &res, 1);
}
int
ukbd_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
{
struct ukbd_softc *sc = v;
struct hidkbd *kbd = &sc->sc_kbd;
int rc;
switch (cmd) {
case WSKBDIO_GTYPE:
*(int *)data = WSKBD_TYPE_USB;
return (0);
case WSKBDIO_SETLEDS:
ukbd_set_leds(v, *(int *)data);
return (0);
default:
rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
if (rc != -1)
return rc;
else
return hidkbd_ioctl(kbd, cmd, data, flag, p);
}
}
void
ukbd_cngetc(void *v, u_int *type, int *data)
{
struct ukbd_softc *sc = v;
struct hidkbd *kbd = &sc->sc_kbd;
DPRINTFN(0,("ukbd_cngetc: enter\n"));
kbd->sc_polling = 1;
while (kbd->sc_npollchar <= 0)
usbd_dopoll(sc->sc_hdev.sc_udev);
kbd->sc_polling = 0;
hidkbd_cngetc(kbd, type, data);
DPRINTFN(0,("ukbd_cngetc: return 0x%02x\n", *data));
}
void
ukbd_cnpollc(void *v, int on)
{
struct ukbd_softc *sc = v;
DPRINTFN(2,("ukbd_cnpollc: sc=%p on=%d\n", v, on));
if (on)
sc->sc_spl = splusb();
else
splx(sc->sc_spl);
usbd_set_polling(sc->sc_hdev.sc_udev, on);
}
void
ukbd_cnbell(void *v, u_int pitch, u_int period, u_int volume)
{
hidkbd_bell(pitch, period, volume, 1);
}
#ifdef DDB
void
ukbd_debugger(void *v)
{
struct ukbd_softc *sc = v;
timeout_add(&sc->sc_ddb, 1);
}
void
ukbd_db_enter(void *xsc)
{
db_enter();
}
#endif
int
ukbd_cnattach(void)
{
struct ukbd_softc *sc;
int i;
hidkbd_is_console = 1;
if (!cold) {
for (i = 0; i < ukbd_cd.cd_ndevs; i++) {
if ((sc = ukbd_cd.cd_devs[i]) != NULL) {
usb_needs_reattach(sc->sc_hdev.sc_udev);
break;
}
}
}
return (0);
}
#ifdef __loongson__
#define GDIUM_FN_CODE 0x82
void
ukbd_gdium_munge(void *vsc, uint8_t *ibuf, u_int ilen)
{
struct ukbd_softc *sc = vsc;
struct hidkbd *kbd = &sc->sc_kbd;
uint8_t *pos, *spos, *epos, xlat;
int fn;
static const struct hidkbd_translation gdium_fn_trans[] = {
#ifdef notyet
{ 58, 0 },
{ 59, 0 },
#endif
{ 60, 127 },
{ 61, 128 },
{ 62, 129 },
#ifdef notyet
{ 63, 0 },
{ 64, 0 },
{ 65, 0 },
{ 66, 0 },
{ 67, 0 },
{ 68, 0 },
{ 69, 0 },
{ 70, 0 },
#endif
{ 76, 71 },
{ 81, 78 },
{ 82, 75 }
};
spos = ibuf + kbd->sc_keycodeloc.pos / 8;
epos = spos + kbd->sc_nkeycode;
fn = 0;
for (pos = spos; pos != epos; pos++)
if (*pos == GDIUM_FN_CODE) {
fn = 1;
*pos = 0;
break;
}
if (fn != 0)
for (pos = spos; pos != epos; pos++) {
xlat = hidkbd_translate(gdium_fn_trans,
nitems(gdium_fn_trans), *pos);
if (xlat != 0)
*pos = xlat;
}
}
#endif