Plan 9 from Bell Labs’s /usr/web/sources/patch/applied/usbcons/devusbcons.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#include	"usb.h"
#include "usbehci.h"

/*
 * USB debug port support for Net20DC Ajays debug cable.
 * Still WIP.
 *
 * ehci must avoid reset of the ctlr whose capio is at
 * ehcidebugcapio, if not nil.
 *
 * Note that using this requires being able to use vmap() to map ehci
 * registers for I/O. This means that MMU code must be initialized.
 *
 * CAUTION: The debug device has two ports but they are NOT
 * the same. If you put the "USB 2.0 debug cable" text in front of you
 * the port to the right is the one to attach to the debug port. The port
 *  on the left is the one to attach to the host used for debugging.
 * Thigs are worse. Power for the "debug cable" is taken only from the
 * port attached to the target machine.
 * This means the device will malfunction unless you do the plug/unplug
 * start serial dance in the wrong order.
 *
 * For the machine I tried, I plug the debugged port and cold start the
 * machine.
 * Then I do this each time I want to start again:
 *	- unplug the debugging port
 *	- warm reboot the debugged machine
 *	- wait until bootfile prompt (device has its power)
 *	- plug the debugging port
 *	- start usb/serail
 *	- con /dev/eiaUX/data
 *
 * If the debug device seems to be stuck I unplug the debugged port
 * to remove power from the device and make it restart.
 *
 * This is so clumsy that the bug might be ours, but I wouldn't bet.
 *
 * The usb/serial driver talking to us must be sure that maxpkt is
 * set to 8 bytes. Otherwise, the debug device hangs. It's picky this cable.
 *
 * There's also some problem in the usb/serial kludge for con'ing this:
 * we miss some messages. However, this could be due to the debugging
 * machine being too slow.
 */

typedef struct Ctlr Ctlr;

enum
{
	Pdebugport	= 0x0A,		/* ehci debug port PCI cap. */

	Ddebug		= 0x0A,		/* debug descriptor type */
	Ddebuglen	= 4,		/* debug descriptor length */
	Debugmode	= 6,		/* debug mode feature */
	Daddr		= 127,		/* debug device address */

	Tokin		= 0x69,
	Tokout		= 0xE1,
	Toksetup	= 0x2D,
	Ack		= 0xD2,
	Nak		= 0x5A,
	Nyet		= 0x96,
	Stall		= 0x1E,

	Data0		= 0xC3 << Pspidshift,
	Data1		= 0x4B << Pspidshift,

	Read		= 0,		/* mode for ucio() */
	Write		= 1,
};

struct Ctlr
{
	Lock;

	Pcidev*	pcidev;
	Ecapio*	capio;		/* Capability i/o regs */
	Eopio*	opio;		/* Operational i/o regs */
	Edbgio*	dbgio;		/* Debug port i/o regs */

	/* software */
	int	port;		/* port number */
	int	epin;		/* input endpoint address */
	int	epout;		/* output endpoint address */

	int	sc;		/* start char in buf */
	int	ec;		/* end char in buf */
	char	buf[8];		/* read buffer */
	Queue*	iq;		/* input queue */
	Queue*	oq;		/* output queue */
	int	consid;		/* console id */
};

#undef dprint
#define dprint	if(debug)iprint

static int debug = 1;
static Ctlr ctlr;		/* one console at most */

static void
ehcirun(Ctlr *ctlr, int on)
{
	int i;
	Eopio *opio;

	dprint("usbcons: %s", on ? "starting" : "halting");
	opio = ctlr->opio;
	if(on)
		opio->cmd |= Crun;
	else
		opio->cmd = Cstop;
	for(i = 0; i < 100; i++)
		if(on == 0 && (opio->sts & Shalted) != 0)
			break;
		else if(on != 0 && (opio->sts & Shalted) == 0)
			break;
		else
			delay(1);
	if(i == 100)
		dprint("usbcons: %s cmd timed out\n", on ? "run" : "halt");
	dprint(" sts %#ulx\n", opio->sts);
}

static int
rpid(ulong csw)
{
	return (csw >> Prpidshift) & Prpidmask;
}

static int
spid(ulong csw)
{
	return (csw >> Pspidshift) & Pspidmask;
}

/*
 * Perform I/O.
 * This returns -1 upon errors but -2 upon naks after (re)trying.
 * The caller could probably retry even more upon naks.
 */
static int
ucio(Edbgio *dbgio, int pid, void *data, int len, int iswrite, int tmout)
{
	static char *wstr[] = {"rd", "wr" };
	int i;
	uchar *b;
	int ntries;
	int once;
	int ppid;

	b = data;
	if(len > 8)
		panic("usbcons ucio bug. len > 0");
	if(len > 0 && data == nil)
		panic("usbcons ucio bug. len but not data");
	ntries = 1000;
	once = 1;
Again:
	dbgio->csw = (dbgio->csw & ~Clen) | len;
	dbgio->pid = pid;
	if(once && debug > 1){
		once = 0;
		dprint("usbcons: %s: csw %#ulx pid %#ux [%d] ",
			wstr[iswrite], dbgio->csw, pid, len);
		if(iswrite){
			for(i = 0; i < len; i++)
				dprint("%02ux ", b[i]);
		}
	}
	memset(dbgio->data, 0, sizeof(dbgio->data));
	if(iswrite){
		if(len > 0)
			memmove(dbgio->data, data, len);
		dbgio->csw |= Cwrite;
	}else
		dbgio->csw &= ~Cwrite;
	dbgio->csw |= Cgo;
	for(i = 0; (dbgio->csw&Cdone) == 0; i++)
		if(tmout != 0 && i > 100000)
			return -1;
	dbgio->csw |= Cdone;	/* acknowledge */

	if((dbgio->csw & Cfailed) != 0){
		dprint(" err csw %#ulx\n", dbgio->csw);
		return -1;
	}

	ppid = rpid(dbgio->pid);
	if((ppid == Nak || ppid == Nyet) && --ntries > 0){
		microdelay(10);
		goto Again;
	}
	if(ntries == 0)
		dprint(" naks");
	if(ppid != Ack && ppid != spid(dbgio->pid)){
		dprint(" bad pid %#x\n", ppid);
		len = -1;
		if(ppid == Nak)
			len = -2;
	}
	if(iswrite == 0 && len > 0){
		if((dbgio->csw&Clen) < len)
			len = dbgio->csw&Clen;
		if(len > 0)
			memmove(data, dbgio->data, len);
	}
	if(debug > 1){
		dprint("-> [%d] ", len);
		if(iswrite == 0){
			for(i = 0; i < len; i++)
				dprint("%02ux ", b[i]);
		}
		dprint(" csw %#ulx\n", dbgio->csw);
	}

	return len;
}

/*
 * BUG: This is not a generic usb cmd tool.
 * If you call this be sure it works for the type of call you are making.
 * This is just for what this driver uses. Compare this with the
 * general purpose ehci control transfer dance.
 */
static int
uccmd(Edbgio *dbgio, int type, int req, int val, int idx, void *data, int cnt)
{
	uchar buf[Rsetuplen];
	int r;

	assert(cnt >= 0 && cnt <= nelem(dbgio->data));
	dprint("usbcons: cmd t %#x r %#x v %#x i %#x c %d\n",
		type, req, val, idx, cnt);
	buf[Rtype] = type;
	buf[Rreq] = req;
	PUT2(buf+Rvalue, val);
	PUT2(buf+Rindex, idx);
	PUT2(buf+Rcount, cnt);
	if(ucio(dbgio, Data0|Toksetup, buf, Rsetuplen, Write, 100) < 0)
		return -1;

	if((type&Rd2h) == 0 && cnt > 0)
		panic("out debug command with data");

	r = ucio(dbgio, Data1|Tokin, data, cnt, Read, 100);
	if((type&Rd2h) != 0)
		ucio(dbgio, Data1|Tokout, nil, 0, Write, 100);
	return r;
}

static ulong
uctoggle(Edbgio *dbgio)
{
	return (dbgio->pid ^ Ptoggle) & Ptogglemask;
}

static int
ucread(Ctlr *ctlr, void *data, int cnt)
{
	Edbgio *dbgio;
	int r;

	ilock(ctlr);
	dbgio = ctlr->dbgio;
	dbgio->addr = (Daddr << Adevshift) | (ctlr->epin << Aepshift);
	r = ucio(dbgio, uctoggle(dbgio)|Tokin, data, cnt, Read, 10);
	if(r < 0)
		uctoggle(dbgio);	/* leave toggle as it was */
	iunlock(ctlr);
	return r;
}

static int
ucwrite(Ctlr *ctlr, void *data, int cnt)
{
	Edbgio *dbgio;
	int r;

	ilock(ctlr);
	dbgio = ctlr->dbgio;
	dbgio->addr = (Daddr << Adevshift) | (ctlr->epout << Aepshift);
	r = ucio(dbgio, uctoggle(dbgio)|Tokout, data, cnt, Write, 10);
	if(r < 0)
		uctoggle(dbgio);	/* leave toggle as it was */
	iunlock(ctlr);
	return r;
}

/*
 * Set the device address to 127 and enable it.
 * The device might have the address hardwired to 127.
 * We try to set it up in any case but ignore most errors.
 */
static int
ucconfigdev(Ctlr *ctlr)
{
	uchar desc[8];
	Edbgio *dbgio;
	int r;

	dbgio = ctlr->dbgio;
	dprint("usbcons: setting up device address to %d\n", Daddr);

	dbgio->addr = (0 << Adevshift) | (0 << Aepshift);
	if(uccmd(dbgio, Rh2d|Rstd|Rdev, Rsetaddr, Daddr, 0, nil, 0) < 0)
		print("usbcons: debug device: can't set address to %d\n", Daddr);
	else
		dprint("usbcons: device address set to %d\n", Daddr);

	dbgio->addr = (Daddr << Adevshift) | (0 << Aepshift);

	dprint("usbcons: reading debug descriptor\n");
	r = uccmd(dbgio, Rd2h|Rstd|Rdev, Rgetdesc, Ddebug << 8, 0, desc, 4);
	if(r < Ddebuglen || desc[1] != Ddebug){
		print("usbcons: debug device: can't get debug descriptor\n");
		dbgio->csw &= ~(Cowner|Cbusy);
		return -1;
	}

	dprint("usbcons: setting up debug mode\n");
	if(uccmd(dbgio, Rh2d|Rstd|Rdev, Rsetfeature, Debugmode, 0, nil, 0) < 0){
		print("usbcons: debug device: can't set debug mode\n");
		dbgio->csw &= ~(Cowner|Cbusy);
		return -1;
	}
	ctlr->epin = desc[2] & ~0x80;	/* clear direction bit from ep. addr */;
	ctlr->epout = desc[3];
	print("#u/usb/ep%d.0: ehci debug port: in ep%d.%d out: ep%d.%d\n",
		Daddr, Daddr, ctlr->epin, Daddr, ctlr->epout);
	return 0;
}

/*
 * Ctlr already ilocked.
 */
static void
portreset(Ctlr *ctlr, int port)
{
	Eopio *opio;
	ulong s;
	int i;

	opio = ctlr->opio;
	s = opio->portsc[port-1];
	dprint("usbcons %#p port %d reset: sts %#ulx\n", ctlr->capio, port, s);
	s &= ~(Psenable|Psreset);
	opio->portsc[port-1] = s|Psreset;
	for(i = 0; i < 10; i++){
		delay(10);
		if((opio->portsc[port-1] & Psreset) == 0)
			break;
	}
	opio->portsc[port-1] &= ~Psreset;
	delay(50);
	dprint("usbcons %#p port %d after reset: sts %#ulx\n", ctlr->capio, port, s);
}

/*
 * Ctlr already ilocked.
 */
static void
portenable(Ctlr *ctlr, int port)
{
	Eopio *opio;
	ulong s;

	opio = ctlr->opio;
	s = opio->portsc[port-1];
	dprint("usbcons: port %d enable: sts %#ulx\n", port, s);
	if(s & (Psstatuschg | Pschange))
		opio->portsc[port-1] = s;
	opio->portsc[port-1] |= Psenable;
	delay(100);
	dprint("usbcons: port %d after enable: sts %#ulx\n", port, s);
}

static int
ucattachdev(Ctlr *ctlr)
{
	Eopio *opio;
	Edbgio *dbgio;
	int i;
	ulong s;
	int port;

	ilock(ctlr);
	if(ehcidebugcapio != nil){
		iunlock(ctlr);
		return -1;
	}

	/* reclaim port */
	dbgio = ctlr->dbgio;
	dbgio->csw |= Cowner;
	dbgio->csw &= ~(Cenable|Cbusy);

	opio = ctlr->opio;
	opio->cmd &= ~(Chcreset|Ciasync|Cpse|Case);
	opio->config = Callmine;	/* reclaim all ports */
	ehcirun(ctlr, 1);
	delay(100);
	ctlr->port = (ctlr->capio->parms >> Cdbgportshift) & Cdbgportmask;
	port = ctlr->port;
	if(port < 1 || port > (ctlr->capio->parms & Cnports)){
		print("usbcons: debug port out of range\n");
		dbgio->csw &= ~(Cowner|Cbusy);
		iunlock(ctlr);
		return -1;
	}
	dprint("usbcons: debug port: %d\n", port);

	opio->portsc[port-1] = Pspower;
	for(i = 0; i < 200; i++){
		s = opio->portsc[port-1];
		if(s & (Psstatuschg | Pschange)){
			opio->portsc[port-1] = s;
			dprint("usbcons: port sts %#ulx\n", s);
		}
		if(s & Pspresent){
			portreset(ctlr, port);
			break;
		}
		delay(1);
	}
	if((opio->portsc[port-1] & Pspresent) == 0 || i == 200){
		print("usbcons: no debug device (attached to another port?)\n");
		dbgio->csw &= ~(Cowner|Cbusy);
		iunlock(ctlr);
		return -1;
	}

	ehcidebugcapio = ctlr->capio;	/* this ehci must avoid reset */
	ehcidebugport = port;		/* and this port must not be available */

	
	dbgio->csw |= Cowner|Cenable|Cbusy;
	opio->portsc[port-1] &= ~Psenable;
	delay(100);
	ehcirun(ctlr, 0);

//	portenable(ctlr, port);
//	dbgio->csw |= Cowner|Cenable|Cbusy;

	delay(100);

	iunlock(ctlr);
	return 0;
}

static int
ucreset(Ctlr *ctlr)
{
	Eopio *opio;
	int i;

	dprint("ucreset\n");
	/*
	 * Turn off legacy mode.
	 */
	ehcirun(ctlr, 0);
	pcicfgw16(ctlr->pcidev, 0xc0, 0x2000);

	/* clear high 32 bits of address signals if it's 64 bits.
	 * This is probably not needed but it does not hurt.
	 */
	opio = ctlr->opio;
	if((ctlr->capio->capparms & C64) != 0){
		dprint("ehci: 64 bits\n");
		opio->seg = 0;
	}

	opio->cmd |= Chcreset;	/* controller reset */
	for(i = 0; i < 100; i++){
		if((opio->cmd & Chcreset) == 0)
			break;
		delay(1);
	}
	if(i == 100){
		print("usbcons: controller reset timed out\n");
		return -1;
	}
	dprint("ucreset done\n");
	return 0;
}

static int
ucinit(Pcidev *p, uint ptr)
{
	uintptr io;
	uint off;
	Ecapio *capio;

	io = p->mem[0].bar & ~0xF;
	if(io == 0){
		print("usbcons: failed to map registers\n");
		return -1;
	}

	off = pcicfgr16(p, ptr+2) & 0xFFF;
	capio = ctlr.capio = vmap(io, p->mem[0].size);
	ctlr.opio = (Eopio*)((uintptr)capio + (capio->cap & 0xFF));
	ctlr.dbgio = (Edbgio*)((uintptr)capio + off);
	ctlr.pcidev = p;
	pcisetbme(p);
	pcisetpms(p, 0);

	if((ctlr.dbgio->csw & Cbusy) != 0){
		print("usbcons: debug port already in use\n");
		return -1;
	}
	print("usbcons: port %#p: ehci debug port\n", ctlr.dbgio);


	if(ucreset(&ctlr) < 0 || ucattachdev(&ctlr) < 0 || ucconfigdev(&ctlr) < 0)
		return -1;

	return 0;
}

/*
 * Polling interface.
 */
int
usbgetc(void)
{
	int nr;

	if(ehcidebugcapio == nil)
		return -1;
	if(ctlr.sc == ctlr.ec){
		ctlr.sc = ctlr.ec = 0;
		nr = ucread(&ctlr, ctlr.buf, sizeof(ctlr.buf));
		if(nr > 0)
			ctlr.ec += nr;
	}
	if(ctlr.sc < ctlr.ec)
		return ctlr.buf[ctlr.sc++];
	
	return -1;
}

void
usbputc(int c)
{
	char buf[1];

	if(ehcidebugcapio == nil)
		return;
	buf[0] = c;
	ucwrite(&ctlr, buf, 1);
}

/*
 * Put 8 chars at a time.
 * Ignore errors (this device seems to be flaky).
 * Some times (for some packets) the device keeps on
 * sending naks. This does not seem to depend on toggles or
 * pids or packet content. It's supposed we don't need to send
 * unstalls here. But it really looks like we do need then.
 * Time to use a sniffer to see what windows does
 * with the device??
 */
void
usbputs(char *s, int n)
{
	int nw;

	if(ehcidebugcapio == nil)
		return;
	for(; n > 0; n -= nw){
		nw = n;
		if(nw > 8)
			nw = 8;
		ucwrite(&ctlr, s, nw);
		s += nw;
	}
}

static Lock lck;

static void
usbclock(void)
{
	char buf[80];
	int n;

	lock(&lck);
	usbputs(".", 1);
	if((n = qconsume(ctlr.oq, buf, sizeof(buf))) > 0)
		usbputs(buf, n);
	unlock(&lck);
//	while((n = ucread(&ctlr, buf, sizeof(buf))) > 0)
//		qproduce(ctlr.iq, buf, n);
}

/*
 * Queue interface
 * The debug port does not seem to
 * issue interrupts for us. We must poll for completion and
 * also to check for input.
 * By now this might suffice but it's probably wrong.
 */
static void
usbconsreset(void)
{
	if(ehcidebugcapio == nil)
		return;
return;
	ctlr.oq = qopen(8*1024, 0, nil, nil);
	if(ctlr.oq == nil)
		return;
	debug = 0;
	addconsdev(ctlr.oq, usbputs, ctlr.consid, 0);
	addclock0link(usbclock, 10);	

	ctlr.iq = qopen(8*1024, 0, nil, nil);
	if(ctlr.iq == nil)
		return;
	addkbdq(ctlr.iq, -1);
}

/*
 * Scan the bus and enable the first debug port found.
 * Perhaps we should search all for a ctlr specified by port
 * e.g., console=usb port=xxxx
 * or by some other means.
 */ 
void
usbconsole(void)
{
	static int already = 0;
	uint ptr;
	Pcidev *p;
	char *s;

	if(already++ != 0)
		return;
	if((s = getconf("console")) == nil || strncmp(s, "usb", 3) != 0)
		if(debug == 0)
			return;
	dprint("usb console...");
	p = nil;
	while ((p = pcimatch(p, 0, 0)) != nil) {
		/*
		 * Find EHCI controllers (Programming Interface = 0x20).
		 */
		if(p->ccrb != Pcibcserial || p->ccru != Pciscusb || p->ccrp != 0x20)
			continue;
		if((pcicfgr16(p, PciPSR) & 0x0010) == 0)
			continue;
		/*
		 * We have extended caps. search list for ehci debug port.
		 */
		ptr = pcicfgr32(p, 0x34);
		while(ptr >= 0x40 && (ptr & ~0xFC) == 0){
			if(pcicfgr8(p, ptr) == Pdebugport){
				dprint("found\n");
				if(ucinit(p, ptr) >= 0){
					ctlr.consid = addconsdev(nil, usbputs, -1, 0);
					print("1");
					print("2");
					print("3");
					print("4");
					print("5");
					print("6");
					print("7");
					print("Plan 9 usb console\n");
				}
				return;
			}
			ptr = pcicfgr8(p, ptr+1);
		}
	}
	dprint("not found\n");
}

/*
 * This driver does not really serve any file. However, we want to
 * setup the console at reset time.
 */

static Chan*
usbconsattach(char*)
{
	error(Eperm);
	return nil;
}

Dev usbconsdevtab = {
	L'↔',
	"usbcons",

	usbconsreset,
	devinit,
	devshutdown,
	usbconsattach,
	/* all others set to nil. Attachments are not allowed */
};

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.