shithub: fork

Download patch

ref: 490f5d73eafc3faf0daa4f8783cf496a5c9063f8
parent: d6e903846391bca5c5b5e683372c339b03eb7279
author: qwx <qwx@sciops.net>
date: Tue Aug 5 06:14:06 EDT 2025

nusb/joy: vid/did for 360 fight stick

--- /dev/null
+++ b/sys/src/cmd/nusb/joy/joy.c
@@ -1,0 +1,568 @@
+/*
+ * USB Human Interaction Device: game controller.
+ *
+ * Game controller events are written to stdout.
+ * May be used in conjunction with a script to
+ * translate to keyboard events and pipe to emulator
+ * (example /sys/src/games/nes/joynes).
+ *
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "hid.h"
+
+typedef struct KDev KDev;
+struct KDev
+{
+	Dev*	dev;		/* usb device*/
+	Dev*	ep;		/* endpoint to get events */
+
+	/* report descriptor */
+	int	nrep;
+	uchar	rep[512];
+};
+
+static void
+kbfree(KDev *kd)
+{
+	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);
+	kbfree(kd);
+	threadexits(sts);
+}
+
+static int debug, kbd;
+static double deadband;
+
+static int
+signext(int v, int bits)
+{
+	int s;
+
+	s = sizeof(v)*8 - bits;
+	v <<= s;
+	v >>= s;
+	return v;
+}
+
+static int
+getbits(uchar *p, uchar *e, int bits, int off)
+{
+	int v, m;
+
+	p += off/8;
+	off %= 8;
+	v = 0;
+	m = 1;
+	if(p < e){
+		while(bits--){
+			if(*p & (1<<off))
+				v |= m;
+			if(++off == 8){
+				if(++p >= e)
+					break;
+				off = 0;
+			}
+			m <<= 1;
+		}
+	}
+	return v;
+}
+
+enum {
+	Ng	= RepCnt+1,
+	UsgCnt	= Delim+1,	/* fake */
+	Nl	= UsgCnt+1,
+	Nu	= 256,
+};
+
+static uchar*
+repparse1(uchar *d, uchar *e, int g[], int l[], int c,
+	void (*f)(int t, int v, int g[], int l[], int c, void *a), void *a)
+{
+	int z, k, t, v, i;
+
+	while(d < e){
+		v = 0;
+		t = *d++;
+		z = t & 3, t >>= 2;
+		k = t & 3, t >>= 2;
+		switch(z){
+		case 3:
+			d += 4;
+			if(d > e) continue;
+			v = d[-4] | d[-3]<<8 | d[-2]<<16 | d[-1]<<24;
+			break;
+		case 2:
+			d += 2;
+			if(d > e) continue;
+			v = d[-2] | d[-1]<<8;
+			break;
+		case 1:
+			d++;
+			if(d > e) continue;
+			v = d[-1];
+			break;
+		}
+		switch(k){
+		case 0:	/* main item*/
+			switch(t){
+			case Collection:
+				memset(l, 0, Nl*sizeof(l[0]));
+				d = repparse1(d, e, g, l, v, f, a);
+				continue;
+			case CollectionEnd:
+				return d;
+			case Input:
+			case Output:
+			case Feature:
+				if(l[UsgCnt] == 0 && l[UsagMin] != 0 && l[UsagMin] < l[UsagMax])
+					for(i=l[UsagMin]; i<=l[UsagMax] && l[UsgCnt] < Nu; i++)
+						l[Nl + l[UsgCnt]++] = i;
+				for(i=0; i<g[RepCnt]; i++){
+					if(i < l[UsgCnt])
+						l[Usage] = l[Nl + i];
+					(*f)(t, v, g, l, c, a);
+				}
+				break;
+			}
+			memset(l, 0, Nl*sizeof(l[0]));
+			continue;
+		case 1:	/* global item */
+			if(t == Push){
+				int w[Ng];
+				memmove(w, g, sizeof(w));
+				d = repparse1(d, e, w, l, c, f, a);
+			} else if(t == Pop){
+				return d;
+			} else if(t < Ng){
+				if(t == RepId)
+					v &= 0xFF;
+				else if(t == UsagPg)
+					v &= 0xFFFF;
+				else if(t != RepSize && t != RepCnt){
+					v = signext(v, (z == 3) ? 32 : 8*z);
+				}
+				g[t] = v;
+			}
+			continue;
+		case 2:	/* local item */
+			if(l[Delim] != 0)
+				continue;
+			if(t == Delim){
+				l[Delim] = 1;
+			} else if(t < Delim){
+				if(z != 3 && (t == Usage || t == UsagMin || t == UsagMax))
+					v = (v & 0xFFFF) | (g[UsagPg] << 16);
+				l[t] = v;
+				if(t == Usage && l[UsgCnt] < Nu)
+					l[Nl + l[UsgCnt]++] = v;
+			}
+			continue;
+		case 3:	/* long item */
+			if(t == 15)
+				d += v & 0xFF;
+			continue;
+		}
+	}
+	return d;
+}
+
+/*
+ * parse the report descriptor and call f for every (Input, Output
+ * and Feature) main item as often as it would appear in the report
+ * data packet.
+ */
+static void
+repparse(uchar *d, uchar *e,
+	void (*f)(int t, int v, int g[], int l[], int c, void *a), void *a)
+{
+	int l[Nl+Nu], g[Ng];
+
+	memset(l, 0, sizeof(l));
+	memset(g, 0, sizeof(g));
+	repparse1(d, e, g, l, 0, f, a);
+}
+
+static int
+setproto(KDev *f, Iface *iface)
+{
+	int proto;
+
+	proto = Bootproto;
+	f->nrep = usbcmd(f->dev, Rd2h|Rstd|Riface, Rgetdesc, Dreport<<8, iface->id, 
+		f->rep, sizeof(f->rep));
+	if(f->nrep > 0){
+		if(debug){
+			int i;
+
+			fprint(2, "report descriptor:");
+			for(i = 0; i < f->nrep; i++){
+				if(i%8 == 0)
+					fprint(2, "\n\t");
+				fprint(2, "%#2.2ux ", f->rep[i]);
+			}
+			fprint(2, "\n");
+		}
+		proto = Reportproto;
+	}
+
+	/*
+	 * if a HID's subclass code is 1 (boot mode), it will support
+	 * setproto, otherwise it is not guaranteed to.
+	 */
+	if(Subclass(iface->csp) != 1)
+		return 0;
+
+	return usbcmd(f->dev, Rh2d|Rclass|Riface, Setproto, proto, iface->id, nil, 0);
+}
+
+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);
+}
+
+typedef struct Joy Joy;
+struct Joy
+{
+	int axes[Maxaxes];
+	int oldaxes[Maxaxes];
+	u64int btns;
+	
+	int	o;
+	uchar	*e;
+	uchar	p[128];
+};
+
+static void
+joyparse(int t, int f, int g[], int l[], int, void *a)
+{
+	int v, i;
+	Joy *p = a;
+	u64int m;
+
+	if(t != Input)
+		return;
+	if(g[RepId] != 0){
+		if(p->p[0] != g[RepId]){
+			p->o = 0;
+			return;
+		}
+		if(p->o < 8)
+			p->o = 8;	/* skip report id byte */
+	}
+	v = getbits(p->p, p->e, g[RepSize], p->o);
+	if(g[LogiMin] < 0)
+		v = signext(v, g[RepSize]);
+	if((f & (Fvar|Farray)) == Fvar && v >= g[LogiMin] && v <= g[LogiMax]){
+		switch(l[Usage]){
+		case 0x010030:
+		case 0x010031:
+		case 0x010032:
+		case 0x010033:
+		case 0x010034:
+		case 0x010035:
+			i = l[Usage] - 0x010030;
+			if((f & (Fabs|Frel)) == Fabs)
+				p->axes[i] = (abs(v)<(g[LogiMax]*deadband))?0:v;
+			else
+				p->axes[i] += (abs(v)<(g[LogiMax]*deadband))?0:v;
+			break;
+		}
+		if((l[Usage] >> 16) == 0x09){
+			m = 1ULL << (l[Usage] & 0x3f);
+			p->btns &= ~m;
+			if(v != 0)
+				p->btns |= m;
+		}
+	}
+	p->o += g[RepSize];
+}
+
+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
+joywork(void *a)
+{
+	char	err[ERRMAX];
+	int	i, c, nerrs;
+	KDev*	f = a;
+	Joy	p;
+	u64int lastb;
+
+	threadsetname("joy %s", f->ep->dir);
+	sethipri();
+
+	memset(&p, 0, sizeof(p));
+	lastb = 0;
+
+	nerrs = 0;
+	for(;;){
+		if(f->ep == nil)
+			kbfatal(f, nil);
+		if(f->ep->maxpkt < 1 || f->ep->maxpkt > sizeof(p.p))
+			kbfatal(f, "joy: weird mouse maxpkt");
+
+		memset(p.p, 0, sizeof(p.p));
+		c = read(f->ep->dfd, p.p, f->ep->maxpkt);
+		if(c <= 0){
+			if(c < 0)
+				rerrstr(err, sizeof(err));
+			else
+				strcpy(err, "zero read");
+			if(++nerrs < 3){
+				fprint(2, "%s: joy: %s: read: %s\n", argv0, f->ep->dir, err);
+				continue;
+			}
+			kbfatal(f, err);
+		}
+		nerrs = 0;
+
+		p.o = 0;
+		p.e = p.p + c;
+		repparse(f->rep, f->rep+f->nrep, joyparse, &p);
+		for(i = 0; i < Maxaxes; i++){
+			if(p.axes[i] != p.oldaxes[i])
+				print("axis %d %d\n", i, p.axes[i]);
+			p.oldaxes[i] = p.axes[i];
+		}
+		for(i = 0; i < 64; i++)
+			if(((lastb ^ p.btns) & (1ULL<<i)) != 0)
+				print("%s %d\n", (p.btns & (1ULL<<i)) != 0 ? "down" : "up", i);
+		lastb = p.btns;
+	}
+}
+
+static void 
+xbox360(KDev *kd)
+{
+	static uchar descr[] = {
+		0x05, 0x01,
+		0x09, 0x05,
+		0xa1, 0x01,
+		0x75, 0x08,	
+		0x95, 0x01,	
+		0x81, 0x01,
+		0x75, 0x08,
+		0x95, 0x01,
+		0x05, 0x01,
+		0x09, 0x3b,
+		0x81, 0x01,
+		0x05, 0x01,	
+		0x09, 0x01,
+		0xa1, 0x00,
+		0x75, 0x01,
+		0x15, 0x00,
+		0x25, 0x01,
+		0x35, 0x00,	
+		0x45, 0x01,	
+		0x95, 0x04,
+		0x05, 0x09,
+		0x09, 0x0c,
+		0x09, 0x0d,
+		0x09, 0x0e,
+		0x09, 0x0f,
+		0x81, 0x02,
+		0xc0,
+		0x75, 0x01,
+		0x15, 0x00,
+		0x25, 0x01,
+		0x35, 0x00,
+		0x45, 0x01,
+		0x95, 0x07,
+		0x05, 0x09,
+		0x09, 0x08,
+		0x09, 0x07,
+		0x09, 0x09,
+		0x09, 0x0a,
+		0x09, 0x05,
+		0x09, 0x06,
+		0x09, 0x0b,
+		0x81, 0x02,
+		0x75, 0x01,
+		0x95, 0x01,
+		0x81, 0x01,
+		0x75, 0x01,
+		0x15, 0x00,
+		0x25, 0x01,
+		0x35, 0x00,
+		0x45, 0x01,
+		0x95, 0x04,
+		0x05, 0x09,
+		0x19, 0x01,
+		0x29, 0x04,
+		0x81, 0x02,	
+		0x75, 0x08,
+		0x15, 0x00,
+		0x26, 0xff, 0x00,
+		0x35, 0x00,
+		0x46, 0xff, 0x00,
+		0x95, 0x02,
+		0x05, 0x01,
+		0x09, 0x32,
+		0x09, 0x35,
+		0x81, 0x02,
+		0x75, 0x10,
+		0x16, 0x00, 0x80,
+		0x26, 0xff, 0x7f,
+		0x36, 0x00, 0x80,
+		0x46, 0xff, 0x7f,
+		0x95, 0x04,
+		0x05, 0x01,
+		0x09, 0x30,
+		0x09, 0x31,
+		0x09, 0x33,
+		0x09, 0x34,
+		0x81, 0x02,
+		0x75, 0x30,
+		0x95, 0x01,
+		0x81, 0x01,
+		0xc0,
+	};
+	static uchar ledcmd[] = {1,3,0};
+	Dev *d = kd->dev;
+
+	memcpy(kd->rep, descr, kd->nrep = sizeof(descr));
+	/* no blinken lights */
+	usbcmd(d, Rh2d|Rclass|Riface, Setreport, Reportout, 0, ledcmd, 3);
+}
+
+
+/* apply quirks for special devices */
+static void
+quirks(KDev *kd)
+{
+	int ret;
+	Dev *d;
+	uchar buf[17];
+
+	d = kd->dev;
+
+	/* sony dualshock 3 (ps3) controller requires special enable command */
+	if(d->usb->vid == 0x054c && d->usb->did == 0x0268){
+		ret = usbcmd(d, Rd2h|Rclass|Riface, Getreport, (0x3<<8) | 0xF2, 0, buf, sizeof(buf));
+		if(ret < 0)
+			sysfatal("failed to enable ps3 controller: %r");
+	}
+
+	/* XBox360 controller and compatible return no HID descriptor, so we provide one */
+	if(d->usb->vid == 0x045e && d->usb->did == 0x028e
+	|| d->usb->vid == 0x1bad && d->usb->did == 0xf03a
+	|| d->usb->vid == 0x2dc8 && d->usb->did == 0x310a)
+		xbox360(kd);
+}
+
+static void
+kbstart(Dev *d, Ep *ep, void (*f)(void*))
+{
+	KDev *kd;
+
+	kd = emallocz(sizeof(KDev), 1);
+	incref(d);
+	kd->dev = d;
+	if(setproto(kd, ep->iface) < 0){
+		fprint(2, "%s: %s: setproto: %r\n", argv0, d->dir);
+		goto Err;
+	}
+	kd->ep = openep(kd->dev, ep);
+	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;
+	}
+	quirks(kd);
+	if(kd->nrep == 0) 
+		kbfatal(kd, "no report");
+
+	f(kd);
+	return;
+Err:
+	kbfree(kd);
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-d] [-b deadband] devid\n", argv0);
+	threadexits("usage");
+}
+
+void
+threadmain(int argc, char* argv[])
+{
+	int i;
+	Dev *d;
+	Ep *ep;
+	Usbdev *ud;
+	char *b;
+
+	ARGBEGIN{
+	case 'd':
+		debug++;
+		break;
+	case 'b':
+		b = EARGF(usage());
+		deadband = atof(b);
+		if(deadband > 0.0 && deadband < 1.0)
+			break;
+	default:
+		usage();
+	}ARGEND;
+	if(argc != 1)
+		usage();
+	d = getdev(*argv);
+	if(d == nil)
+		sysfatal("getdev: %r");
+	ud = d->usb;
+	ep = nil;
+	for(i = 0; i < nelem(ud->ep); i++){
+		if((ep = ud->ep[i]) == nil)
+			continue;
+		if(ep->type != Eintr || (ep->dir == Eout))
+			continue;
+		if(ep->iface->csp == JoyCSP) 
+			break;
+		if(ep->iface->csp == Xbox360CSP)
+			break;
+	}
+	if(ep == nil)
+		sysfatal("no suitable endpoint found");
+	kbstart(d, ep, joywork);
+	threadexits(nil);
+}
--