diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/usb/kb/kb.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/usb/kb/kb.c')
-rwxr-xr-x | sys/src/cmd/usb/kb/kb.c | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/sys/src/cmd/usb/kb/kb.c b/sys/src/cmd/usb/kb/kb.c new file mode 100755 index 000000000..090a270dc --- /dev/null +++ b/sys/src/cmd/usb/kb/kb.c @@ -0,0 +1,601 @@ +/* + * USB Human Interaction Device: keyboard and mouse. + * + * If there's no usb keyboard, it tries to setup the mouse, if any. + * It should be started at boot time. + * + * Mouse events are converted to the format of mouse(3)'s + * mousein file. + * Keyboard keycodes are translated to scan codes and sent to kbin(3). + * + */ + +#include <u.h> +#include <libc.h> +#include <thread.h> +#include "usb.h" +#include "hid.h" + +enum +{ + Awakemsg=0xdeaddead, + Diemsg = 0xbeefbeef, +}; + +typedef struct KDev KDev; +typedef struct Kin Kin; + +struct KDev +{ + Dev* dev; /* usb device*/ + Dev* ep; /* endpoint to get events */ + Kin* in; /* used to send events to kernel */ + Channel*repeatc; /* only for keyboard */ + int accel; /* only for mouse */ +}; + +/* + * Kbdin and mousein files must be shared among all instances. + */ +struct Kin +{ + int ref; + int fd; + char* name; +}; + +/* + * Map for the logitech bluetooth mouse with 8 buttons and wheels. + * { ptr ->mouse} + * { 0x01, 0x01 }, // left + * { 0x04, 0x02 }, // middle + * { 0x02, 0x04 }, // right + * { 0x40, 0x08 }, // up + * { 0x80, 0x10 }, // down + * { 0x10, 0x08 }, // side up + * { 0x08, 0x10 }, // side down + * { 0x20, 0x02 }, // page + * besides wheel and regular up/down report the 4th byte as 1/-1 + */ + +/* + * key code to scan code; for the page table used by + * the logitech bluetooth keyboard. + */ +static char sctab[256] = +{ +[0x00] 0x0, 0x0, 0x0, 0x0, 0x1e, 0x30, 0x2e, 0x20, +[0x08] 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, +[0x10] 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1f, 0x14, +[0x18] 0x16, 0x2f, 0x11, 0x2d, 0x15, 0x2c, 0x2, 0x3, +[0x20] 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, +[0x28] 0x1c, 0x1, 0xe, 0xf, 0x39, 0xc, 0xd, 0x1a, +[0x30] 0x1b, 0x2b, 0x2b, 0x27, 0x28, 0x29, 0x33, 0x34, +[0x38] 0x35, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, +[0x40] 0x41, 0x42, 0x43, 0x44, 0x57, 0x58, 0x63, 0x46, +[0x48] 0x77, 0x52, 0x47, 0x49, 0x53, 0x4f, 0x51, 0x4d, +[0x50] 0x4b, 0x50, 0x48, 0x45, 0x35, 0x37, 0x4a, 0x4e, +[0x58] 0x1c, 0x4f, 0x50, 0x51, 0x4b, 0x4c, 0x4d, 0x47, +[0x60] 0x48, 0x49, 0x52, 0x53, 0x56, 0x7f, 0x74, 0x75, +[0x68] 0x55, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, +[0x70] 0x78, 0x79, 0x7a, 0x7b, 0x0, 0x0, 0x0, 0x0, +[0x78] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71, +[0x80] 0x73, 0x72, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, +[0x88] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0x90] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0x98] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xa0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xa8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xb0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xb8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xc0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xc8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xd0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xd8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xe0] 0x1d, 0x2a, 0x38, 0x7d, 0x61, 0x36, 0x64, 0x7e, +[0xe8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x73, 0x72, 0x71, +[0xf0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +[0xf8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +}; + +static QLock inlck; +static Kin kbdin = +{ + .ref = 0, + .name = "#Ι/kbin", + .fd = -1, +}; +static Kin ptrin = +{ + .ref = 0, + .name = "#m/mousein", + .fd = -1, +}; + +static int kbdebug; + +static int +setbootproto(KDev* f, int eid) +{ + int r, id; + + r = Rh2d|Rclass|Riface; + id = f->dev->usb->ep[eid]->iface->id; + return usbcmd(f->dev, r, Setproto, Bootproto, id, nil, 0); +} + +/* + * Try to recover from a babble error. A port reset is the only way out. + * BUG: we should be careful not to reset a bundle with several devices. + */ +static void +recoverkb(KDev *f) +{ + int i; + + close(f->dev->dfd); /* it's for usbd now */ + devctl(f->dev, "reset"); + for(i = 0; i < 10; i++){ + sleep(500); + if(opendevdata(f->dev, ORDWR) >= 0){ + setbootproto(f, f->ep->id); + break; + } + /* else usbd still working... */ + } +} + +static void +kbfatal(KDev *kd, char *sts) +{ + Dev *dev; + + if(sts != nil) + fprint(2, "kb: fatal: %s\n", sts); + else + fprint(2, "kb: exiting\n"); + if(kd->repeatc != nil) + nbsendul(kd->repeatc, Diemsg); + dev = kd->dev; + kd->dev = nil; + if(kd->ep != nil) + closedev(kd->ep); + kd->ep = nil; + devctl(dev, "detach"); + closedev(dev); + /* + * free(kd); done by closedev. + */ + threadexits(sts); +} + +static int +scale(KDev *f, int x) +{ + int sign = 1; + + if(x < 0){ + sign = -1; + x = -x; + } + switch(x){ + case 0: + case 1: + case 2: + case 3: + break; + case 4: + x = 6 + (f->accel>>2); + break; + case 5: + x = 9 + (f->accel>>1); + break; + default: + x *= MaxAcc; + break; + } + return sign*x; +} + +/* + * ps2 mouse is processed mostly at interrupt time. + * for usb we do what we can. + */ +static void +sethipri(void) +{ + char fn[30]; + int fd; + + snprint(fn, sizeof(fn), "/proc/%d/ctl", getpid()); + fd = open(fn, OWRITE); + if(fd < 0) + return; + fprint(fd, "pri 13"); + close(fd); +} + +static void +ptrwork(void* a) +{ + static char maptab[] = {0x0, 0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7}; + int x, y, b, c, ptrfd; + int mfd, nerrs; + char buf[32]; + char mbuf[80]; + KDev* f = a; + int hipri; + + hipri = nerrs = 0; + ptrfd = f->ep->dfd; + mfd = f->in->fd; + + if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf) + kbfatal(f, "weird mouse maxpkt"); + for(;;){ + memset(buf, 0, sizeof buf); + if(f->ep == nil) + kbfatal(f, nil); + c = read(ptrfd, buf, f->ep->maxpkt); + assert(f->dev != nil); + assert(f->ep != nil); + if(c < 0){ + dprint(2, "kb: mouse: %s: read: %r\n", f->ep->dir); + if(++nerrs < 3){ + recoverkb(f); + continue; + } + } + if(c <= 0) + kbfatal(f, nil); + if(c < 3) + continue; + if(f->accel){ + x = scale(f, buf[1]); + y = scale(f, buf[2]); + }else{ + x = buf[1]; + y = buf[2]; + } + b = maptab[buf[0] & 0x7]; + if(c > 3 && buf[3] == 1) /* up */ + b |= 0x08; + if(c > 3 && buf[3] == -1) /* down */ + b |= 0x10; + if(kbdebug > 1) + fprint(2, "kb: m%11d %11d %11d\n", x, y, b); + seprint(mbuf, mbuf+sizeof(mbuf), "m%11d %11d %11d", x, y,b); + if(write(mfd, mbuf, strlen(mbuf)) < 0) + kbfatal(f, "mousein i/o"); + if(hipri == 0){ + sethipri(); + hipri = 1; + } + } +} + +static void +stoprepeat(KDev *f) +{ + sendul(f->repeatc, Awakemsg); +} + +static void +startrepeat(KDev *f, uchar esc1, uchar sc) +{ + ulong c; + + if(esc1) + c = SCesc1 << 8 | (sc & 0xff); + else + c = sc; + sendul(f->repeatc, c); +} + +static void +putscan(int kbinfd, uchar esc, uchar sc) +{ + uchar s[2] = {SCesc1, 0}; + + if(sc == 0x41){ + kbdebug += 2; + return; + } + if(sc == 0x42){ + kbdebug = 0; + return; + } + if(kbdebug) + fprint(2, "sc: %x %x\n", (esc? SCesc1: 0), sc); + s[1] = sc; + if(esc && sc != 0) + write(kbinfd, s, 2); + else if(sc != 0) + write(kbinfd, s+1, 1); +} + +static void +repeatproc(void* a) +{ + KDev *f; + Channel *repeatc; + int kbdinfd; + ulong l, t, i; + uchar esc1, sc; + + /* + * too many jumps here. + * Rewrite instead of debug, if needed. + */ + f = a; + repeatc = f->repeatc; + kbdinfd = f->in->fd; + l = Awakemsg; +Repeat: + if(l == Diemsg) + goto Abort; + while(l == Awakemsg) + l = recvul(repeatc); + if(l == Diemsg) + goto Abort; + esc1 = l >> 8; + sc = l; + t = 160; + for(;;){ + for(i = 0; i < t; i += 5){ + if(l = nbrecvul(repeatc)) + goto Repeat; + sleep(5); + } + putscan(kbdinfd, esc1, sc); + t = 30; + } +Abort: + chanfree(repeatc); + threadexits("aborted"); + +} + + +#define hasesc1(sc) (((sc) > 0x47) || ((sc) == 0x38)) + +static void +putmod(int fd, uchar mods, uchar omods, uchar mask, uchar esc, uchar sc) +{ + /* BUG: Should be a single write */ + if((mods&mask) && !(omods&mask)) + putscan(fd, esc, sc); + if(!(mods&mask) && (omods&mask)) + putscan(fd, esc, Keyup|sc); +} + +/* + * This routine diffs the state with the last known state + * and invents the scan codes that would have been sent + * by a non-usb keyboard in that case. This also requires supplying + * the extra esc1 byte as well as keyup flags. + * The aim is to allow future addition of other keycode pages + * for other keyboards. + */ +static uchar +putkeys(KDev *f, uchar buf[], uchar obuf[], int n, uchar dk) +{ + int i, j; + uchar uk; + int fd; + + fd = f->in->fd; + putmod(fd, buf[0], obuf[0], Mctrl, 0, SCctrl); + putmod(fd, buf[0], obuf[0], (1<<Mlshift), 0, SClshift); + putmod(fd, buf[0], obuf[0], (1<<Mrshift), 0, SCrshift); + putmod(fd, buf[0], obuf[0], Mcompose, 0, SCcompose); + putmod(fd, buf[0], obuf[0], Maltgr, 1, SCcompose); + + /* Report key downs */ + for(i = 2; i < n; i++){ + for(j = 2; j < n; j++) + if(buf[i] == obuf[j]) + break; + if(j == n && buf[i] != 0){ + dk = sctab[buf[i]]; + putscan(fd, hasesc1(dk), dk); + startrepeat(f, hasesc1(dk), dk); + } + } + + /* Report key ups */ + uk = 0; + for(i = 2; i < n; i++){ + for(j = 2; j < n; j++) + if(obuf[i] == buf[j]) + break; + if(j == n && obuf[i] != 0){ + uk = sctab[obuf[i]]; + putscan(fd, hasesc1(uk), uk|Keyup); + } + } + if(uk && (dk == 0 || dk == uk)){ + stoprepeat(f); + dk = 0; + } + return dk; +} + +static int +kbdbusy(uchar* buf, int n) +{ + int i; + + for(i = 1; i < n; i++) + if(buf[i] == 0 || buf[i] != buf[0]) + return 0; + return 1; +} + +static void +kbdwork(void *a) +{ + int c, i, kbdfd, nerrs; + uchar dk, buf[64], lbuf[64]; + char err[128]; + KDev *f = a; + + kbdfd = f->ep->dfd; + + if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf) + kbfatal(f, "weird maxpkt"); + + f->repeatc = chancreate(sizeof(ulong), 0); + if(f->repeatc == nil) + kbfatal(f, "chancreate failed"); + + proccreate(repeatproc, f, Stack); + memset(lbuf, 0, sizeof lbuf); + dk = nerrs = 0; + for(;;){ + memset(buf, 0, sizeof buf); + c = read(kbdfd, buf, f->ep->maxpkt); + assert(f->dev != nil); + assert(f->ep != nil); + if(c < 0){ + rerrstr(err, sizeof(err)); + fprint(2, "kb: %s: read: %s\n", f->ep->dir, err); + if(strstr(err, "babble") != 0 && ++nerrs < 3){ + recoverkb(f); + continue; + } + } + if(c <= 0) + kbfatal(f, nil); + if(c < 3) + continue; + if(kbdbusy(buf + 2, c - 2)) + continue; + if(usbdebug > 2 || kbdebug > 1){ + fprint(2, "kbd mod %x: ", buf[0]); + for(i = 2; i < c; i++) + fprint(2, "kc %x ", buf[i]); + fprint(2, "\n"); + } + dk = putkeys(f, buf, lbuf, f->ep->maxpkt, dk); + memmove(lbuf, buf, c); + nerrs = 0; + } +} + +static void +freekdev(void *a) +{ + KDev *kd; + + kd = a; + if(kd->in != nil){ + qlock(&inlck); + if(--kd->in->ref == 0){ + close(kd->in->fd); + kd->in->fd = -1; + } + qunlock(&inlck); + } + dprint(2, "freekdev\n"); + free(kd); +} + +static void +kbstart(Dev *d, Ep *ep, Kin *in, void (*f)(void*), int accel) +{ + KDev *kd; + + qlock(&inlck); + if(in->fd < 0){ + in->fd = open(in->name, OWRITE); + if(in->fd < 0){ + fprint(2, "kb: %s: %r\n", in->name); + qunlock(&inlck); + return; + } + } + in->ref++; /* for kd->in = in */ + qunlock(&inlck); + kd = d->aux = emallocz(sizeof(KDev), 1); + d->free = freekdev; + kd->in = in; + kd->dev = d; + if(setbootproto(kd, ep->id) < 0){ + fprint(2, "kb: %s: bootproto: %r\n", d->dir); + return; + } + kd->accel = accel; + kd->ep = openep(d, ep->id); + if(kd->ep == nil){ + fprint(2, "kb: %s: openep %d: %r\n", d->dir, ep->id); + return; + } + if(opendevdata(kd->ep, OREAD) < 0){ + fprint(2, "kb: %s: opendevdata: %r\n", kd->ep->dir); + closedev(kd->ep); + kd->ep = nil; + return; + } + + incref(d); + proccreate(f, kd, Stack); +} + +static int +usage(void) +{ + werrstr("usage: usb/kb [-dkm] [-a n] [-N nb]"); + return -1; +} + +int +kbmain(Dev *d, int argc, char* argv[]) +{ + int i, kena, pena, accel, devid; + Usbdev *ud; + Ep *ep; + + kena = pena = 1; + accel = 0; + devid = d->id; + ARGBEGIN{ + case 'a': + accel = strtol(EARGF(usage()), nil, 0); + break; + case 'd': + kbdebug++; + break; + case 'k': + kena = 1; + pena = 0; + break; + case 'm': + kena = 0; + pena = 1; + break; + case 'N': + devid = atoi(EARGF(usage())); /* ignore dev number */ + break; + default: + return usage(); + }ARGEND; + if(argc != 0){ + return usage(); + } + USED(devid); + ud = d->usb; + d->aux = nil; + dprint(2, "kb: main: dev %s ref %ld\n", d->dir, d->ref); + for(i = 0; i < nelem(ud->ep); i++){ + if((ep = ud->ep[i]) == nil) + break; + if(kena && ep->type == Eintr && ep->dir == Ein) + if(ep->iface->csp == KbdCSP) + kbstart(d, ep, &kbdin, kbdwork, accel); + if(pena && ep->type == Eintr && ep->dir == Ein) + if(ep->iface->csp == PtrCSP) + kbstart(d, ep, &ptrin, ptrwork, accel); + } + return 0; +} |