text: /* Copyright (c) 20XX 9front
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in all
   * copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   * SOFTWARE.
   */
  #include <u.h>
  #include <lib9.h>
  #include <thread.h>
  #include "usb.h"
  
  /*
   * epN.M -> N
   */
  static int
  nameid(char *s)
  {
  	char *r;
  	char nm[20];
  
  	r = strrchr(s, 'p');
  	if(r == nil)
  		return -1;
  	strecpy(nm, nm+sizeof(nm), r+1);
  	r = strchr(nm, '.');
  	if(r == nil)
  		return -1;
  	*r = 0;
  	return atoi(nm);
  }
  
  Dev*
  openep(Dev *d, int id)
  {
  	char *mode;	/* How many modes? */
  	Ep *ep;
  	Altc *ac;
  	Dev *epd;
  	Usbdev *ud;
  	char name[40];
  
  	if(access("/dev/usb", AEXIST) < 0 && sys_bind("#u", "/dev", MBEFORE) < 0)
  		return nil;
  	if(d->cfd < 0 || d->usb == nil){
  		werrstr("device not configured");
  		return nil;
  	}
  	ud = d->usb;
  	if(id < 0 || id >= nelem(ud->ep) || ud->ep[id] == nil){
  		werrstr("bad enpoint number");
  		return nil;
  	}
  	ep = ud->ep[id];
  	mode = "rw";
  	if(ep->dir == Ein)
  		mode = "r";
  	if(ep->dir == Eout)
  		mode = "w";
  	snprint(name, sizeof(name), "/dev/usb/ep%d.%d", d->id, id);
  	if(access(name, AEXIST) == 0){
  		dprint(2, "%s: %s already exists; trying to open\n", argv0, name);
  		epd = opendev(name);
  		if(epd != nil)
  			epd->maxpkt = ep->maxpkt;	/* guess */
  		return epd;
  	}
  	if(devctl(d, "new %d %d %s", id, ep->type, mode) < 0){
  		dprint(2, "%s: %s: new: %r\n", argv0, d->dir);
  		return nil;
  	}
  	epd = opendev(name);
  	if(epd == nil)
  		return nil;
  	epd->id = id;
  	if(devctl(epd, "maxpkt %d", ep->maxpkt) < 0)
  		fprint(2, "%s: %s: openep: maxpkt: %r\n", argv0, epd->dir);
  	else
  		dprint(2, "%s: %s: maxpkt %d\n", argv0, epd->dir, ep->maxpkt);
  	epd->maxpkt = ep->maxpkt;
  	ac = ep->iface->altc[0];
  	if(ep->ntds > 1 && devctl(epd, "ntds %d", ep->ntds) < 0)
  		fprint(2, "%s: %s: openep: ntds: %r\n", argv0, epd->dir);
  	else
  		dprint(2, "%s: %s: ntds %d\n", argv0, epd->dir, ep->ntds);
  
  	/*
  	 * For iso endpoints and high speed interrupt endpoints the pollival is
  	 * actually 2ⁿ and not n.
  	 * The kernel usb driver must take that into account.
  	 * It's simpler this way.
  	 */
  
  	if(ac != nil && (ep->type == Eintr || ep->type == Eiso) && ac->interval != 0)
  		if(devctl(epd, "pollival %d", ac->interval) < 0)
  			fprint(2, "%s: %s: openep: pollival: %r\n", argv0, epd->dir);
  	return epd;
  }
  
  Dev*
  opendev(char *fn)
  {
  	Dev *d;
  	int l;
  
  	if(access("/dev/usb", AEXIST) < 0 && sys_bind("#u", "/dev", MBEFORE) < 0)
  		return nil;
  	d = emallocz(sizeof(Dev), 1);
  	incref(&d->ref);
  
  	l = strlen(fn);
  	d->dfd = -1;
  	/*
  	 * +30 to allocate extra size to concat "/<epfilename>"
  	 * we should probably remove that feature from the manual
  	 * and from the code after checking out that nobody relies on
  	 * that.
  	 */
  	d->dir = emallocz(l + 30, 0);
  	strcpy(d->dir, fn);
  	strcpy(d->dir+l, "/ctl");
  	d->cfd = sys_open(d->dir, ORDWR|OCEXEC);
  	d->dir[l] = 0;
  	d->id = nameid(fn);
  	if(d->cfd < 0){
  		werrstr("can't open endpoint %s: %r", d->dir);
  		free(d->dir);
  		free(d);
  		return nil;
  	}
  	d->hname = nil;
  	dprint(2, "%s: opendev %#p %s\n", argv0, d, fn);
  	return d;
  }
  
  int
  opendevdata(Dev *d, int mode)
  {
  	char buf[80]; /* more than enough for a usb path */
  
  	seprint(buf, buf+sizeof(buf), "%s/data", d->dir);
  	d->dfd = sys_open(buf, mode|OCEXEC);
  	return d->dfd;
  }
  
  enum
  {
  	/*
  	 * Max device conf is also limited by max control request size as
  	 * limited by Maxctllen in the kernel usb.h (both limits are arbitrary).
  	 */
  	Maxdevconf = 4 * 1024,	/* asking for 16K kills Newsham's disk */
  };
  
  int
  loaddevconf(Dev *d, int n)
  {
  	uint8_t *buf;
  	int nr;
  	int type;
  
  	if(n >= nelem(d->usb->conf)){
  		werrstr("loaddevconf: bug: out of configurations in device");
  		fprint(2, "%s: %r\n", argv0);
  		return -1;
  	}
  	buf = emallocz(Maxdevconf, 0);
  	type = Rd2h|Rstd|Rdev;
  	nr = usbcmd(d, type, Rgetdesc, Dconf<<8|n, 0, buf, Maxdevconf);
  	if(nr < Dconflen){
  		free(buf);
  		return -1;
  	}
  	if(d->usb->conf[n] == nil)
  		d->usb->conf[n] = emallocz(sizeof(Conf), 1);
  	nr = parseconf(d->usb, d->usb->conf[n], buf, nr);
  	free(buf);
  	return nr;
  }
  
  Ep*
  mkep(Usbdev *d, int id)
  {
  	Ep *ep;
  
  	d->ep[id] = ep = emallocz(sizeof(Ep), 1);
  	ep->id = id;
  	return ep;
  }
  
  static char*
  mkstr(uint8_t *b, int n)
  {
  	Rune r;
  	char *us;
  	char *s;
  
  	if(n > 0 && n > b[0])
  		n = b[0];
  	if(n <= 2 || (n & 1) != 0)
  		return strdup("none");
  	n = (n - 2)/2;
  	b += 2;
  	us = s = emallocz(n*UTFmax+1, 0);
  	for(; --n >= 0; b += 2){
  		r = GET2(b);
  		s += runetochar(s, &r);
  	}
  	*s = 0;
  	return us;
  }
  
  char*
  loaddevstr(Dev *d, int sid)
  {
  	uint8_t buf[256];
  	int langid;
  	int type;
  	int nr;
  
  	if(sid == 0)
  		return estrdup("none");
  	type = Rd2h|Rstd|Rdev;
  
  	/*
  	 * there are devices which do not return a string if used
  	 * with invalid language id, so at least try to use the first
  	 * one and choose english if failed
  	 */
  	nr=usbcmd(d, type, Rgetdesc, Dstr<<8, 0, buf, sizeof(buf));
  	if(nr < 4)
  		langid = 0x0409;	// english
  	else
  		langid = buf[3]<<8|buf[2];
  
  	nr=usbcmd(d, type, Rgetdesc, Dstr<<8|sid, langid, buf, sizeof(buf));
  	return mkstr(buf, nr);
  }
  
  int
  loaddevdesc(Dev *d)
  {
  	uint8_t buf[Ddevlen];
  	int nr;
  	int type;
  	Ep *ep0;
  
  	type = Rd2h|Rstd|Rdev;
  	nr = sizeof(buf);
  	memset(buf, 0, nr);
  	if((nr=usbcmd(d, type, Rgetdesc, Ddev<<8|0, 0, buf, nr)) < 0)
  		return -1;
  	/*
  	 * Several hubs are returning descriptors of 17 bytes, not 18.
  	 * We accept them and leave number of configurations as zero.
  	 * (a get configuration descriptor also fails for them!)
  	 */
  	if(nr < Ddevlen){
  		print("%s: %s: warning: device with short descriptor\n",
  			argv0, d->dir);
  		if(nr < Ddevlen-1){
  			werrstr("short device descriptor (%d bytes)", nr);
  			return -1;
  		}
  	}
  	d->usb = emallocz(sizeof(Usbdev), 1);
  	ep0 = mkep(d->usb, 0);
  	ep0->dir = Eboth;
  	ep0->type = Econtrol;
  	ep0->maxpkt = d->maxpkt = 8;		/* a default */
  	nr = parsedev(d, buf, nr);
  	if(nr >= 0){
  		d->usb->vendor = loaddevstr(d, d->usb->vsid);
  		if(strcmp(d->usb->vendor, "none") != 0){
  			d->usb->product = loaddevstr(d, d->usb->psid);
  			d->usb->serial = loaddevstr(d, d->usb->ssid);
  		}
  	}
  	return nr;
  }
  
  int
  configdev(Dev *d)
  {
  	int i;
  
  	if(d->dfd < 0)
  		opendevdata(d, ORDWR);
  	if(d->dfd < 0)
  		return -1;
  	if(loaddevdesc(d) < 0)
  		return -1;
  	for(i = 0; i < d->usb->nconf; i++)
  		if(loaddevconf(d, i) < 0)
  			return -1;
  	return 0;
  }
  
  static void
  closeconf(Conf *c)
  {
  	int i;
  	int a;
  
  	if(c == nil)
  		return;
  	for(i = 0; i < nelem(c->iface); i++)
  		if(c->iface[i] != nil){
  			for(a = 0; a < nelem(c->iface[i]->altc); a++)
  				free(c->iface[i]->altc[a]);
  			free(c->iface[i]);
  		}
  	free(c);
  }
  
  void
  closedev(Dev *d)
  {
  	int i;
  	Usbdev *ud;
  
  	if(d==nil || decref(&d->ref) != 0)
  		return;
  	dprint(2, "%s: closedev %#p %s\n", argv0, d, d->dir);
  	if(d->free != nil)
  		d->free(d->aux);
  	if(d->cfd >= 0)
  		sys_close(d->cfd);
  	if(d->dfd >= 0)
  		sys_close(d->dfd);
  	d->cfd = d->dfd = -1;
  	free(d->dir);
  	d->dir = nil;
  	free(d->hname);
  	d->hname = nil;
  	ud = d->usb;
  	d->usb = nil;
  	if(ud != nil){
  		free(ud->vendor);
  		free(ud->product);
  		free(ud->serial);
  		for(i = 0; i < nelem(ud->ep); i++)
  			free(ud->ep[i]);
  		for(i = 0; i < nelem(ud->ddesc); i++)
  			free(ud->ddesc[i]);
  
  		for(i = 0; i < nelem(ud->conf); i++)
  			closeconf(ud->conf[i]);
  		free(ud);
  	}
  	free(d);
  }
  
  static char*
  reqstr(int type, int req)
  {
  	char *s;
  	static char* ds[] = { "dev", "if", "ep", "oth" };
  	static char buf[40];
  
  	if(type&Rd2h)
  		s = seprint(buf, buf+sizeof(buf), "d2h");
  	else
  		s = seprint(buf, buf+sizeof(buf), "h2d");
  	if(type&Rclass)
  		s = seprint(s, buf+sizeof(buf), "|cls");
  	else if(type&Rvendor)
  		s = seprint(s, buf+sizeof(buf), "|vnd");
  	else
  		s = seprint(s, buf+sizeof(buf), "|std");
  	s = seprint(s, buf+sizeof(buf), "|%s", ds[type&3]);
  
  	switch(req){
  	case Rgetstatus: s = seprint(s, buf+sizeof(buf), " getsts"); break;
  	case Rclearfeature: s = seprint(s, buf+sizeof(buf), " clrfeat"); break;
  	case Rsetfeature: s = seprint(s, buf+sizeof(buf), " setfeat"); break;
  	case Rsetaddress: s = seprint(s, buf+sizeof(buf), " setaddr"); break;
  	case Rgetdesc: s = seprint(s, buf+sizeof(buf), " getdesc"); break;
  	case Rsetdesc: s = seprint(s, buf+sizeof(buf), " setdesc"); break;
  	case Rgetconf: s = seprint(s, buf+sizeof(buf), " getcnf"); break;
  	case Rsetconf: s = seprint(s, buf+sizeof(buf), " setcnf"); break;
  	case Rgetiface: s = seprint(s, buf+sizeof(buf), " getif"); break;
  	case Rsetiface: s = seprint(s, buf+sizeof(buf), " setif"); break;
  	}
  	USED(s);
  	return buf;
  }
  
  static int
  cmdreq(Dev *d, int type, int req, int value, int index, uint8_t *data, int count)
  {
  	int ndata, n;
  	uint8_t *wp;
  	uint8_t buf[8];
  	char *hd, *rs;
  
  	assert(d != nil);
  	if(data == nil){
  		wp = buf;
  		ndata = 0;
  	}else{
  		ndata = count;
  		wp = emallocz(8+ndata, 0);
  	}
  	wp[0] = type;
  	wp[1] = req;
  	PUT2(wp+2, value);
  	PUT2(wp+4, index);
  	PUT2(wp+6, count);
  	if(data != nil)
  		memmove(wp+8, data, ndata);
  	if(usbdebug>2){
  		hd = hexstr(wp, ndata+8);
  		rs = reqstr(type, req);
  		fprint(2, "%s: %s val %d|%d idx %d cnt %d out[%d] %s\n",
  			d->dir, rs, value>>8, value&0xFF,
  			index, count, ndata+8, hd);
  		free(hd);
  	}
  	n = jehanne_write(d->dfd, wp, 8+ndata);
  	if(wp != buf)
  		free(wp);
  	if(n < 0)
  		return -1;
  	if(n != 8+ndata){
  		dprint(2, "%s: cmd: short write: %d\n", argv0, n);
  		return -1;
  	}
  	return n;
  }
  
  static int
  cmdrep(Dev *d, void *buf, int nb)
  {
  	char *hd;
  
  	nb = jehanne_read(d->dfd, buf, nb);
  	if(nb >0 && usbdebug > 2){
  		hd = hexstr(buf, nb);
  		fprint(2, "%s: in[%d] %s\n", d->dir, nb, hd);
  		free(hd);
  	}
  	return nb;
  }
  
  int
  usbcmd(Dev *d, int type, int req, int value, int index, uint8_t *data, int count)
  {
  	int i, r, nerr;
  	char err[64];
  
  	/*
  	 * Some devices do not respond to commands some times.
  	 * Others even report errors but later work just fine. Retry.
  	 */
  	r = -1;
  	*err = 0;
  	for(i = nerr = 0; i < Uctries; i++){
  		if(type & Rd2h)
  			r = cmdreq(d, type, req, value, index, nil, count);
  		else
  			r = cmdreq(d, type, req, value, index, data, count);
  		if(r > 0){
  			if((type & Rd2h) == 0)
  				break;
  			r = cmdrep(d, data, count);
  			if(r > 0)
  				break;
  			if(r == 0)
  				werrstr("no data from device");
  		}
  		nerr++;
  		if(*err == 0)
  			rerrstr(err, sizeof(err));
  		sleep(Ucdelay);
  	}
  	if(r > 0 && i >= 2)
  		/* let the user know the device is not in good shape */
  		fprint(2, "%s: usbcmd: %s: required %d attempts (%s)\n",
  			argv0, d->dir, i, err);
  	return r;
  }
  
  int
  unstall(Dev *dev, Dev *ep, int dir)
  {
  	int r;
  
  	if(dir == Ein)
  		dir = 0x80;
  	else
  		dir = 0;
  	r = Rh2d|Rstd|Rep;
  	if(usbcmd(dev, r, Rclearfeature, Fhalt, (ep->id&0xF)|dir, nil, 0)<0){
  		werrstr("unstall: %s: %r", ep->dir);
  		return -1;
  	}
  	if(devctl(ep, "clrhalt") < 0){
  		werrstr("clrhalt: %s: %r", ep->dir);
  		return -1;
  	}
  	return 0;
  }
  
  /*
   * To be sure it uses a single write.
   */
  int
  devctl(Dev *dev, char *fmt, ...)
  {
  	char buf[128];
  	va_list arg;
  	char *e;
  
  	va_start(arg, fmt);
  	e = vseprint(buf, buf+sizeof(buf), fmt, arg);
  	va_end(arg);
  	return jehanne_write(dev->cfd, buf, e-buf);
  }
  
  Dev *
  getdev(char *devid)
  {
  	char buf[40], *p;
  	Dev *d;
  
  	if(devid[0] == '/' || devid[0] == '#'){
  		snprint(buf, sizeof buf, "%s", devid);
  		p = strrchr(buf, '/');
  		if(p != nil){
  			p = strrchr(buf, ':');
  			if(p != nil)
  				*p = 0;
  		}
  	} else {
  		p = nil;
  		snprint(buf, sizeof buf, "/dev/usb/ep%ld.0", strtol(devid, &p, 10));
  		if(*p != ':')
  			p = nil;
  	}
  
  	d = opendev(buf);
  	if(d == nil)
  		return nil;
  	if(configdev(d) < 0){
  		closedev(d);
  		return nil;
  	}
  
  	if(p == nil){
  		snprint(buf, sizeof buf, ":%d", d->id);
  		p = buf;
  	}
  	d->hname = strdup(p+1);
  
  	return d;
  }
  