shithub: riscv

ref: d99e693119812727d3267e2e02e55be660e98a1a
dir: /sys/src/cmd/nusb/kb/kb.c/

View raw version
/*
 * 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)
 * on mousein file.
 * Keyboard keycodes are translated to scan codes and sent to kbdfs(8)
 * on kbin file.
 *
 */

#include <u.h>
#include <libc.h>
#include <thread.h>
#include "usb.h"
#include "hid.h"

enum
{
	Awakemsg=0xdeaddead,
	Diemsg = 0xbeefbeef,
};

enum
{
	Kbdelay = 500,
	Kbrepeat = 100,
};

typedef struct KDev KDev;
struct KDev
{
	Dev*	dev;		/* usb device*/
	Dev*	ep;		/* endpoint to get events */
	int	infd;		/* used to send events to kernel */
	Channel*repeatc;	/* only for keyboard */
};

/*
 * 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 int kbdebug;
static int accel;

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);
}

static int
setleds(KDev* f, int, uchar leds)
{
	return usbcmd(f->dev, Rh2d|Rclass|Riface, Setreport, Reportout, 0, &leds, 1);
}

/*
 * 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
kbfree(KDev *kd)
{
	if(kd->infd >= 0)
		close(kd->infd);
	if(kd->ep != nil)
		closedev(kd->ep);
	if(kd->dev != nil)
		closedev(kd->dev);
	free(kd);
}

static void
kbfatal(KDev *kd, char *sts)
{
	if(sts != nil)
		fprint(2, "%s: fatal: %s\n", argv0, sts);
	else
		fprint(2, "%s: exiting\n", argv0);
	if(kd->repeatc != nil)
		sendul(kd->repeatc, Diemsg);
	kbfree(kd);
	threadexits(sts);
}

static void
kbprocname(KDev *kd, char *name)
{
	char buf[128];
	snprint(buf, sizeof(buf), "%s %s", name, kd->ep->dir);
	threadsetname(buf);
}

static void
sethipri(void)
{
	char fn[64];
	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 int
scale(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 + (accel>>2);
		break;
	case 5:
		x = 9 + (accel>>1);
		break;
	default:
		x *= MaxAcc;
		break;
	}
	return sign*x;
}

static short
s16(void *p)
{
	uchar *b = p;
	return b[0] | b[1]<<8;
}

static void
ptrwork(void* a)
{
	static char maptab[] = {0x0, 0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7};
	int x, y, z, b, c, nerrs, skiplead;
	char	err[ERRMAX], buf[64];
	char	mbuf[80];
	KDev*	f = a;

	kbprocname(f, "ptrwork");
	sethipri();

	skiplead = -1;
	nerrs = 0;
	for(;;){
		if(f->ep == nil)
			kbfatal(f, nil);
		if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf)
			kbfatal(f, "mouse: weird mouse maxpkt");
		memset(buf, 0, sizeof buf);
		c = read(f->ep->dfd, buf, f->ep->maxpkt);
		if(c <= 0){
			if(c < 0)
				rerrstr(err, sizeof(err));
			else
				strcpy(err, "zero read");
			if(++nerrs < 3){
				fprint(2, "%s: mouse: %s: read: %s\n", argv0, f->ep->dir, err);
				if(strstr(err, "babble") != 0)
					recoverkb(f);
				continue;
			}
			kbfatal(f, err);
		}
		nerrs = 0;
		if(c < 3)
			continue;

		if(c > 4){
			/*
			 * horrible horrible hack. some mouse send a
			 * constant 0x01 lead byte. stop the hack as
			 * soon as buf[0] changes.
			 */
			if(skiplead == -1)
				skiplead = buf[0] & 0xFF;
			if(skiplead == 0x01 && skiplead == buf[0])
				memmove(buf, buf+1, --c);
			else
				skiplead = 0;
		}

		z = 0;
		if(c == 6 && f->dev->usb->vid == 0x1a7c){
			/* Evoluent vertical mouse */
			x = s16(&buf[1]);
			y = s16(&buf[3]);
			z = buf[5];
		} else {
			x = buf[1];
			y = buf[2];
			if(c > 3)
				z = buf[3];
		}
		if(accel){
			x = scale(x);
			y = scale(y);
		}
		b = maptab[buf[0] & 0x7];
		if(z > 0)	/* up */
			b |= 0x08;
		if(z < 0)	/* down */
			b |= 0x10;
		if(kbdebug > 1)
			fprint(2, "%s: m%11d %11d %11d\n", argv0, x, y, b);
		seprint(mbuf, mbuf+sizeof(mbuf), "m%11d %11d %11d", x, y,b);
		if(write(f->infd, mbuf, strlen(mbuf)) < 0)
			kbfatal(f, "mousein i/o");
	}
}

static void
putscan(int fd, uchar esc, uchar sc)
{
	uchar s[2] = {SCesc1, 0};

	s[1] = sc;
	if(esc && sc != 0)
		write(fd, s, 2);
	else if(sc != 0)
		write(fd, s+1, 1);
}

static void
putmod(int fd, uchar mods, uchar omods, uchar mask, uchar esc, uchar sc)
{
	uchar s[4], *p;

	p = s;
	if((mods&mask) && !(omods&mask)){
		if(esc)
			*p++ = SCesc1;
		*p++ = sc;
	}
	if(!(mods&mask) && (omods&mask)){
		if(esc)
			*p++ = SCesc1;
		*p++ = Keyup|sc;
	}
	if(p > s)
		write(fd, s, p - s);
}

static void
sleepproc(void* a)
{
	Channel *c = a;
	int ms;

	threadsetname("sleepproc");
	while((ms = recvul(c)) > 0)
		sleep(ms);
	chanfree(c);
}

static void
repeatproc(void* arg)
{
	KDev *f = arg;
	Channel *repeatc, *sleepc;
	int kbdinfd;
	ulong l, t;
	uchar esc1, sc;
	Alt a[3];

	repeatc = f->repeatc;
	kbdinfd = f->infd;
	threadsetname("repeatproc");
	
	sleepc = chancreate(sizeof(ulong), 0);
	if(sleepc != nil)
		proccreate(sleepproc, sleepc, Stack);

	a[0].c = repeatc;
	a[0].v = &l;
	a[0].op = CHANRCV;
	a[1].c = sleepc;
	a[1].v = &t;
	a[1].op = sleepc!=nil ? CHANSND : CHANNOP;
	a[2].c = nil;
	a[2].v = nil;
	a[2].op = CHANEND;

	l = Awakemsg;
	while(l != Diemsg){
		if(l == Awakemsg){
			l = recvul(repeatc);
			continue;
		}
		esc1 = l >> 8;
		sc = l;
		t = Kbdelay;
		if(alt(a) == 1){
			t = Kbrepeat;
			while(alt(a) == 1)
				putscan(kbdinfd, esc1, sc);
		}
	}
	if(sleepc != nil)
		sendul(sleepc, 0);
	chanfree(repeatc);
	threadexits(nil);
}

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);
}

#define hasesc1(sc)	(((sc) > 0x47) || ((sc) == 0x38))

/*
 * 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->infd;
	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)
{
	uchar dk, buf[64], lbuf[64];
	int c, i, nerrs;
	char err[128];
	KDev *f = a;

	kbprocname(f, "kbdwork");

	f->repeatc = chancreate(sizeof(ulong), 0);
	if(f->repeatc == nil)
		kbfatal(f, "chancreate failed");
	proccreate(repeatproc, f, Stack);

	setleds(f, f->ep->id, 0);
	sethipri();

	memset(lbuf, 0, sizeof lbuf);
	dk = nerrs = 0;
	for(;;){
		if(f->ep == nil)
			kbfatal(f, nil);
		if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf)
			kbfatal(f, "kbd: weird maxpkt");
		memset(buf, 0, sizeof buf);
		c = read(f->ep->dfd, buf, f->ep->maxpkt);
		if(c <= 0){
			if(c < 0)
				rerrstr(err, sizeof(err));
			else
				strcpy(err, "zero read");
			if(++nerrs < 3){
				fprint(2, "%s: kbd: %s: read: %s\n", argv0, f->ep->dir, err);
				if(strstr(err, "babble") != 0)
					recoverkb(f);
				continue;
			}
			kbfatal(f, err);
		}
		nerrs = 0;
		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);
	}
}

static void
kbstart(Dev *d, Ep *ep, char *infile, void (*f)(void*))
{
	KDev *kd;

	kd = emallocz(sizeof(KDev), 1);
	kd->infd = open(infile, OWRITE);
	if(kd->infd < 0){
		fprint(2, "%s: %s: open: %r\n", argv0, d->dir);
		goto Err;
	}
	incref(d);
	kd->dev = d;
	if(setbootproto(kd, ep->id) < 0){
		fprint(2, "%s: %s: bootproto: %r\n", argv0, d->dir);
		goto Err;
	}
	kd->ep = openep(kd->dev, ep->id);
	if(kd->ep == nil){
		fprint(2, "%s: %s: openep %d: %r\n", argv0, d->dir, ep->id);
		goto Err;
	}
	if(opendevdata(kd->ep, OREAD) < 0){
		fprint(2, "%s: %s: opendevdata: %r\n", argv0, kd->ep->dir);
		goto Err;
	}
	procrfork(f, kd, Stack, RFNOTEG);
	return;
Err:
	kbfree(kd);
}

static void
usage(void)
{
	fprint(2, "usage: %s [-d] [-a n] devid\n", argv0);
	threadexits("usage");
}

void
threadmain(int argc, char* argv[])
{
	int i;
	Dev *d;
	Ep *ep;
	Usbdev *ud;

	ARGBEGIN{
	case 'a':
		accel = strtol(EARGF(usage()), nil, 0);
		break;
	case 'd':
		kbdebug++;
		break;
	default:
		usage();
	}ARGEND;
	if(argc != 1)
		usage();
	d = getdev(atoi(*argv));
	if(d == nil)
		sysfatal("getdev: %r");
	ud = d->usb;
	for(i = 0; i < nelem(ud->ep); i++){
		if((ep = ud->ep[i]) == nil)
			break;
		if(ep->type == Eintr && ep->dir == Ein && ep->iface->csp == KbdCSP)
			kbstart(d, ep, "/dev/kbin", kbdwork);
		if(ep->type == Eintr && ep->dir == Ein && ep->iface->csp == PtrCSP)
			kbstart(d, ep, "/dev/mousein", ptrwork);
	}
	threadexits(nil);
}