/* $OpenBSD: dnkbd.c,v 1.3 2005/04/22 13:13:19 miod Exp $ */ /* * Copyright (c) 2005, Miodrag Vallat * Copyright (c) 1997 Michael Smith. 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * Driver for the Apollo Domain keyboard and mouse. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WSDISPLAY_COMPAT_RAWKBD #include #endif #include #include #include #include #include #include #include #include "hilkbd.h" /* * Keyboard key codes */ #define DNKEY_REPEAT 0x7f #define DNKEY_RELEASE 0x80 #define DNKEY_CHANNEL 0xff /* * Channels */ #define DNCHANNEL_RESET 0x00 #define DNCHANNEL_KBD 0x01 #define DNCHANNEL_MOUSE 0x02 /* * Keyboard modes */ #define DNMODE_COOKED 0x00 #define DNMODE_RAW 0x01 /* * Keyboard commands */ #define DNCMD_PREFIX 0xff #define DNCMD_COOKED DNMODE_COOKED #define DNCMD_RAW DNMODE_RAW #define DNCMD_IDENT_1 0x12 #define DNCMD_IDENT_2 0x21 /* * Mouse status */ #define DNBUTTON_L 0x10 #define DNBUTTON_R 0x20 #define DNBUTTON_M 0x40 struct dnkbd_softc { struct device sc_dev; struct isr sc_isr; struct apciregs *sc_regs; int sc_flags; #define SF_ENABLED 0x01 /* keyboard enabled */ #define SF_CONSOLE 0x02 /* keyboard is console */ #define SF_POLLING 0x04 /* polling mode */ #define SF_PLUGGED 0x08 /* keyboard has been seen plugged */ #define SF_ATTACHED 0x10 /* subdevices have been attached */ #define SF_MOUSE 0x20 /* mouse enabled */ u_int sc_identlen; #define MAX_IDENTLEN 32 char sc_ident[MAX_IDENTLEN]; enum { STATE_KEYBOARD, STATE_MOUSE, STATE_CHANNEL } sc_state; u_int8_t sc_mousepkt[3]; /* mouse packet being constructed */ u_int sc_mousepos; /* index in above */ struct device *sc_wskbddev; struct device *sc_wsmousedev; #ifdef WSDISPLAY_COMPAT_RAWKBD int sc_rawkbd; int sc_nrep; char sc_rep[2]; /* at most, one key */ struct timeout sc_rawrepeat_ch; #define REP_DELAY1 400 #define REP_DELAYN 100 #endif }; int dnkbd_match(struct device *, void *, void *); void dnkbd_attach(struct device *, struct device *, void *); struct cfdriver dnkbd_cd = { NULL, "dnkbd", DV_DULL }; struct cfattach dnkbd_ca = { sizeof(struct dnkbd_softc), dnkbd_match, dnkbd_attach }; int dnkbd_enable(void *, int); void dnkbd_set_leds(void *, int); int dnkbd_ioctl(void *, u_long, caddr_t, int, struct proc *); const struct wskbd_accessops dnkbd_accessops = { dnkbd_enable, dnkbd_set_leds, dnkbd_ioctl }; int dnmouse_enable(void *); int dnmouse_ioctl(void *, u_long, caddr_t, int, struct proc *); void dnmouse_disable(void *); const struct wsmouse_accessops dnmouse_accessops = { dnmouse_enable, dnmouse_ioctl, dnmouse_disable }; void dnkbd_cngetc(void *, u_int *, int *); void dnkbd_cnpollc(void *, int); const struct wskbd_consops dnkbd_consops = { dnkbd_cngetc, dnkbd_cnpollc, NULL /* bell */ }; struct wskbd_mapdata dnkbd_keymapdata = { dnkbd_keydesctab, KB_US }; typedef enum { EVENT_NONE, EVENT_KEYBOARD, EVENT_MOUSE } dnevent; void dnevent_kbd(struct dnkbd_softc *, int); void dnevent_mouse(struct dnkbd_softc *, u_int8_t *); void dnkbd_attach_subdevices(struct dnkbd_softc *); void dnkbd_decode(int, u_int *, int *); int dnkbd_init(struct apciregs *); dnevent dnkbd_input(struct dnkbd_softc *, int); int dnkbd_intr(void *); int dnkbd_pollin(struct apciregs *, u_int); int dnkbd_pollout(struct apciregs *, int); int dnkbd_probe(struct dnkbd_softc *); void dnkbd_rawrepeat(void *); int dnkbd_send(struct apciregs *, u_int8_t *, size_t); int dnsubmatch_kbd(struct device *, void *, void *); int dnsubmatch_mouse(struct device *, void *, void *); int dnkbd_match(struct device *parent, void *match, void *aux) { struct frodo_attach_args *fa = aux; if (strcmp(fa->fa_name, dnkbd_cd.cd_name) != 0) return (0); /* only attach to the first frodo port */ return (fa->fa_offset == FRODO_APCI_OFFSET(0)); } void dnkbd_attach(struct device *parent, struct device *self, void *aux) { struct dnkbd_softc *sc = (struct dnkbd_softc *)self; struct frodo_attach_args *fa = aux; printf(": "); sc->sc_regs = (struct apciregs *)IIOV(FRODO_BASE + fa->fa_offset); #ifdef WSDISPLAY_COMPAT_RAWKBD timeout_set(&sc->sc_rawrepeat_ch, dnkbd_rawrepeat, sc); #endif /* reset the port */ apciinit(sc->sc_regs, 1200, CFCR_8BITS | CFCR_PEVEN | CFCR_PENAB); sc->sc_isr.isr_func = dnkbd_intr; sc->sc_isr.isr_arg = sc; sc->sc_isr.isr_priority = IPL_TTY; frodo_intr_establish(parent, fa->fa_line, &sc->sc_isr, self->dv_xname); /* probe for keyboard */ if (dnkbd_probe(sc) != 0) { printf("no keyboard\n"); return; } dnkbd_attach_subdevices(sc); } void dnkbd_attach_subdevices(struct dnkbd_softc *sc) { struct wskbddev_attach_args ka; struct wsmousedev_attach_args ma; #if NHILKBD > 0 extern int hil_is_console; #endif /* * If both hilkbd and dnkbd are configured, prefer the Domain * keyboard as console (if we are here, we know the keyboard is * plugged). But if it's a late hotplug, the hil code will have * claimed the console keyboard, so don't claim it now. * Unfortunately, the hil code will claim the console keyboard * even if no HIL keyboard is connected... */ #if NHILKBD > 0 if (hil_is_console == -1) { ka.console = 1; hil_is_console = 0; } else ka.console = 0; #else ka.console = 1; #endif ka.keymap = &dnkbd_keymapdata; ka.accessops = &dnkbd_accessops; ka.accesscookie = sc; if (ka.console) { sc->sc_flags = SF_PLUGGED | SF_CONSOLE | SF_ENABLED; wskbd_cnattach(&dnkbd_consops, sc, &dnkbd_keymapdata); } else { sc->sc_flags = SF_PLUGGED; } sc->sc_wskbddev = config_found_sm(&sc->sc_dev, &ka, wskbddevprint, dnsubmatch_kbd); ma.accessops = &dnmouse_accessops; ma.accesscookie = sc; sc->sc_wsmousedev = config_found_sm(&sc->sc_dev, &ma, wsmousedevprint, dnsubmatch_mouse); SET(sc->sc_flags, SF_ATTACHED); } int dnsubmatch_kbd(struct device *parent, void *match, void *aux) { struct cfdata *cf = match; extern struct cfdriver wskbd_cd; if (strcmp(cf->cf_driver->cd_name, wskbd_cd.cd_name) != 0) return (0); return ((*cf->cf_attach->ca_match)(parent, cf, aux)); } int dnsubmatch_mouse(struct device *parent, void *match, void *aux) { struct cfdata *cf = match; extern struct cfdriver wsmouse_cd; if (strcmp(cf->cf_driver->cd_name, wsmouse_cd.cd_name) != 0) return (0); return ((*cf->cf_attach->ca_match)(parent, cf, aux)); } int dnkbd_probe(struct dnkbd_softc *sc) { int dat, rc, flags; u_int8_t cmdbuf[2]; char rspbuf[MAX_IDENTLEN], *word, *end; u_int i; int s; s = spltty(); flags = sc->sc_flags; SET(sc->sc_flags, SF_POLLING); sc->sc_state = STATE_CHANNEL; splx(s); /* * Switch keyboard to raw mode. */ cmdbuf[0] = DNCMD_RAW; rc = dnkbd_send(sc->sc_regs, cmdbuf, 1); if (rc != 0) goto out; /* * Send the identify command. */ cmdbuf[0] = DNCMD_IDENT_1; cmdbuf[1] = DNCMD_IDENT_2; rc = dnkbd_send(sc->sc_regs, cmdbuf, 2); if (rc != 0) goto out; for (i = 0; ; i++) { dat = dnkbd_pollin(sc->sc_regs, 10000); if (dat == -1) break; if (i < sizeof(rspbuf)) rspbuf[i] = dat; } if (i >= sizeof(rspbuf)) { printf("%s: unexpected identify string length %d\n", sc->sc_dev.dv_xname, i); rc = ENXIO; goto out; } /* * Make sure the identification string is NULL terminated * (overwriting the keyboard mode byte if necessary). */ if (i == sizeof(rspbuf) && dat != 0) rspbuf[--i] = 0; /* * Now display the identification strings, if it changed. */ if (i != sc->sc_identlen || bcmp(rspbuf, sc->sc_ident, i) != 0) { sc->sc_identlen = i; bcopy(rspbuf, sc->sc_ident, i); if (cold == 0) printf("%s: ", sc->sc_dev.dv_xname); word = rspbuf; for (i = 0; i < 3; i++) { end = strchr(word, '\r'); if (end == NULL) break; *end++ = '\0'; printf("<%s> ", word); word = end; } printf("\n"); } out: s = spltty(); sc->sc_flags = flags; splx(s); return (rc); } /* * State machine. * * The keyboard may feed us the following sequences: * - a raw key code, in the range 0x01-0x7e, or'ed with 0x80 if key release. * - the key repeat sequence 0x7f. * - a 3 byte mouse sequence. * - a 2 byte channel sequence (0xff followed by the channel number) telling * us which device the following input will come from. */ dnevent dnkbd_input(struct dnkbd_softc *sc, int dat) { dnevent event = EVENT_NONE; switch (sc->sc_state) { case STATE_KEYBOARD: switch (dat) { case DNKEY_REPEAT: /* * We ignore event repeats, as wskbd does its own * soft repeat processing. */ break; case DNKEY_CHANNEL: sc->sc_state = STATE_CHANNEL; break; default: event = EVENT_KEYBOARD; break; } break; case STATE_MOUSE: if (dat == DNKEY_CHANNEL && sc->sc_mousepos == 0) { sc->sc_state = STATE_CHANNEL; } else { sc->sc_mousepkt[sc->sc_mousepos++] = dat; if (sc->sc_mousepos == sizeof(sc->sc_mousepkt)) { sc->sc_mousepos = 0; event = EVENT_MOUSE; } } break; case STATE_CHANNEL: switch (dat) { case DNKEY_CHANNEL: /* * During hotplug, we might get spurious 0xff bytes. * Ignore them. */ break; case DNCHANNEL_RESET: /* * Identify the keyboard again. This will switch it to * raw mode again. If this fails, we'll consider the * keyboard as unplugged (to ignore further events until * a successfull reset). */ if (dnkbd_probe(sc) == 0) { /* * We need to attach wskbd and wsmouse children * if this is a live first plug. */ if (!ISSET(sc->sc_flags, SF_ATTACHED)) dnkbd_attach_subdevices(sc); SET(sc->sc_flags, SF_PLUGGED); } else { CLR(sc->sc_flags, SF_PLUGGED); } sc->sc_state = STATE_KEYBOARD; break; case DNCHANNEL_KBD: sc->sc_state = STATE_KEYBOARD; break; case DNCHANNEL_MOUSE: sc->sc_state = STATE_MOUSE; sc->sc_mousepos = 0; /* just in case */ break; default: printf("%s: unexpected channel byte %02x\n", sc->sc_dev.dv_xname, dat); break; } break; } return (event); } /* * Event breakers. */ void dnkbd_decode(int keycode, u_int *type, int *key) { *type = (keycode & DNKEY_RELEASE) ? WSCONS_EVENT_KEY_UP : WSCONS_EVENT_KEY_DOWN; *key = (keycode & ~DNKEY_RELEASE); } void dnevent_kbd(struct dnkbd_softc *sc, int dat) { u_int type; int key; int s; if (!ISSET(sc->sc_flags, SF_PLUGGED)) return; if (sc->sc_wskbddev == NULL) return; if (!ISSET(sc->sc_flags, SF_ENABLED)) return; dnkbd_decode(dat, &type, &key); #ifdef WSDISPLAY_COMPAT_RAWKBD if (sc->sc_rawkbd) { u_char cbuf[2]; int c, j = 0; c = dnkbd_raw[key]; if (c != RAWKEY_Null) { /* fake extended scancode if necessary */ if (c & 0x80) cbuf[j++] = 0xe0; cbuf[j] = c & 0x7f; if (type == WSCONS_EVENT_KEY_UP) cbuf[j] |= 0x80; else { /* remember pressed key for autorepeat */ bcopy(cbuf, sc->sc_rep, sizeof(sc->sc_rep)); } } s = spltty(); wskbd_rawinput(sc->sc_wskbddev, cbuf, j); splx(s); timeout_del(&sc->sc_rawrepeat_ch); sc->sc_nrep = j; if (j != 0) { timeout_add(&sc->sc_rawrepeat_ch, (hz * REP_DELAY1) / 1000); } } else #endif { s = spltty(); wskbd_input(sc->sc_wskbddev, type, key); splx(s); } } #ifdef WSDISPLAY_COMPAT_RAWKBD void dnkbd_rawrepeat(void *v) { struct dnkbd_softc *sc = v; int s; s = spltty(); wskbd_rawinput(sc->sc_wskbddev, sc->sc_rep, sc->sc_nrep); splx(s); timeout_add(&sc->sc_rawrepeat_ch, (hz * REP_DELAYN) / 1000); } #endif void dnevent_mouse(struct dnkbd_softc *sc, u_int8_t *dat) { if (!ISSET(sc->sc_flags, SF_PLUGGED)) return; if (sc->sc_wsmousedev == NULL) return; if (!ISSET(sc->sc_flags, SF_MOUSE)) return; /* * First byte is button status. It has the 0x80 bit always set, and * the next 3 bits are *cleared* when the mouse buttons are pressed. */ #ifdef DEBUG if (!ISSET(*dat, 0x80)) { printf("%s: incorrect mouse packet %02x %02x %02x\n", sc->sc_dev.dv_xname, dat[0], dat[1], dat[2]); return; } #endif wsmouse_input(sc->sc_wsmousedev, (~dat[0] & (DNBUTTON_L | DNBUTTON_M | DNBUTTON_R)) >> 4, (int8_t)dat[1], (int8_t)dat[2], 0, WSMOUSE_INPUT_DELTA); } /* * Low-level communication routines. */ int dnkbd_pollin(struct apciregs *apci, u_int tries) { u_int cnt; for (cnt = tries; cnt != 0; cnt--) { if (apci->ap_lsr & LSR_RXRDY) break; DELAY(10); } if (cnt == 0) return (-1); else return ((int)apci->ap_data); } int dnkbd_pollout(struct apciregs *apci, int dat) { u_int cnt; for (cnt = 10000; cnt != 0; cnt--) { if (apci->ap_lsr & LSR_TXRDY) break; DELAY(10); } if (cnt == 0) return (EBUSY); else { apci->ap_data = dat; return (0); } } int dnkbd_send(struct apciregs *apci, u_int8_t *cmdbuf, size_t cmdlen) { int cnt, rc, dat; u_int cmdpos; /* drain rxfifo */ for (cnt = 10; cnt != 0; cnt--) { if (dnkbd_pollin(apci, 10) == -1) break; } if (cnt == 0) return (EBUSY); /* send command escape */ if ((rc = dnkbd_pollout(apci, DNCMD_PREFIX)) != 0) return (rc); /* send command buffer */ for (cmdpos = 0; cmdpos < cmdlen; cmdpos++) { if ((rc = dnkbd_pollout(apci, cmdbuf[cmdpos])) != 0) return (rc); } /* wait for command echo */ do { dat = dnkbd_pollin(apci, 10000); if (dat == -1) return (EIO); } while (dat != DNCMD_PREFIX); for (cmdpos = 0; cmdpos < cmdlen; cmdpos++) { dat = dnkbd_pollin(apci, 10000); if (dat != cmdbuf[cmdpos]) return (EIO); } return (0); } int dnkbd_intr(void *v) { struct dnkbd_softc *sc = v; struct apciregs *apci = sc->sc_regs; u_int8_t iir, lsr, c; int claimed = 0; for (;;) { iir = apci->ap_iir; switch (iir & IIR_IMASK) { case IIR_RLS: /* * Line status change. This should never happen, * so silently ack the interrupt. */ c = apci->ap_lsr; break; case IIR_RXRDY: case IIR_RXTOUT: /* * Data available. We process it byte by byte, * unless we are doing polling work... */ if (ISSET(sc->sc_flags, SF_POLLING)) { return (1); } for (;;) { c = apci->ap_data; switch (dnkbd_input(sc, c)) { case EVENT_KEYBOARD: dnevent_kbd(sc, c); break; case EVENT_MOUSE: dnevent_mouse(sc, sc->sc_mousepkt); break; default: /* appease gcc */ break; } lsr = apci->ap_lsr & LSR_RCV_MASK; if (lsr == 0) break; else if (lsr != LSR_RXRDY) { /* ignore error */ break; } } break; case IIR_TXRDY: /* * Transmit available. Since we do all our commands * in polling mode, we do not need to do anything here. */ break; default: if (iir & IIR_NOPEND) return (claimed); /* FALLTHROUGH */ case IIR_MLSC: /* * Modem status change. This should never happen, * so silently ack the interrupt. */ c = apci->ap_msr; break; } claimed = 1; } } /* * Wskbd callbacks */ int dnkbd_enable(void *v, int on) { struct dnkbd_softc *sc = v; if (on) { if (ISSET(sc->sc_flags, SF_ENABLED)) return (EBUSY); SET(sc->sc_flags, SF_ENABLED); } else { if (ISSET(sc->sc_flags, SF_CONSOLE)) return (EBUSY); CLR(sc->sc_flags, SF_ENABLED); } return (0); } void dnkbd_set_leds(void *v, int leds) { /* * Not supported. There is only one LED on this keyboard, and * is hardware tied to the caps lock key. */ } int dnkbd_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p) { struct dnkbd_softc *sc = v; switch (cmd) { case WSKBDIO_GTYPE: *(int *)data = WSKBD_TYPE_DOMAIN; return (0); case WSKBDIO_SETLEDS: return (ENXIO); case WSKBDIO_GETLEDS: *(int *)data = 0; return (0); #ifdef WSDISPLAY_COMPAT_RAWKBD case WSKBDIO_SETMODE: sc->sc_rawkbd = *(int *)data == WSKBD_RAW; timeout_del(&sc->sc_rawrepeat_ch); return (0); #endif } return (-1); } /* * Wsmouse callbacks */ int dnmouse_enable(void *v) { struct dnkbd_softc *sc = v; if (ISSET(sc->sc_flags, SF_MOUSE)) return (EBUSY); SET(sc->sc_flags, SF_MOUSE); return (0); } int dnmouse_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p) { #if 0 struct dnkbd_softc *sc = v; #endif switch (cmd) { case WSMOUSEIO_GTYPE: *(int *)data = WSMOUSE_TYPE_DOMAIN; return (0); } return (-1); } void dnmouse_disable(void *v) { struct dnkbd_softc *sc = v; CLR(sc->sc_flags, SF_MOUSE); } /* * Console support */ void dnkbd_cngetc(void *v, u_int *type, int *data) { struct dnkbd_softc *sc = v; int s; int dat; for (;;) { s = splhigh(); dat = dnkbd_pollin(sc->sc_regs, 10000); if (dat != -1) { if (dnkbd_input(sc, dat) == EVENT_KEYBOARD) { splx(s); break; } } splx(s); } dnkbd_decode(dat, type, data); } void dnkbd_cnpollc(void *v, int on) { struct dnkbd_softc *sc = v; if (on) SET(sc->sc_flags, SF_POLLING); else CLR(sc->sc_flags, SF_POLLING); }