Browse Source

Plan 9 from Bell Labs 2010-04-24

David du Colombier 14 years ago
parent
commit
374fbeabe3

+ 8 - 6
sys/doc/port.ms

@@ -341,21 +341,23 @@ for information about installing Plan 9 on the iPAQ.
 .SH
 The Marvell Kirkwood operating system
 .PP
-This is an ARM kernel that emulates floating-point and
+This is an ARM kernel for the ARM926EJ-S processor
+and it emulates floating-point and
 CAS (compare-and-swap) instructions.
 It is known to run on the Sheevaplug and Openrd-client boards.
 It is derived from a port of native Inferno to the Sheevaplug
 by Salva Peir\f(Jpó\fP and Mechiel Lukkien.
 There are many features of the Kirkwood system-on-a-chip
 that it does not exploit.
-Initially, there are drivers for up to two
-Gigabit Ethernet interfaces and the console serial port;
-we hope to add USB and flash memory drivers,
-and a video driver for the Openrd-client.
+There are currently drivers for up to two
+Gigabit Ethernet interfaces,
+USB and the console serial port;
+we hope to add crypto acceleration, and a video driver for the Openrd-client.
 .SH
 The TI OMAP3530 operating system
 .PP
-This is an ARM kernel that emulates pre-VFPv3 floating-point and
+This is an ARM kernel for the Cortex-A8 processor
+and it emulates pre-VFPv3 floating-point and
 CAS (compare-and-swap) instructions.
 It is known to run on the IGEPv2 board and might
 eventually run on the Beagleboard, once USB is working.

+ 154 - 144
sys/doc/port.ps

@@ -5826,160 +5826,170 @@ mark
 (The Marvell Kirkwood operating system) 720 840 w
 10 /LucidaSansUnicode00 f
 (This) 970 996 w
-(is) 1218 996 w
-(an) 1341 996 w
-(ARM) 1501 996 w
-(kernel) 1762 996 w
-(that) 2108 996 w
-(emulates) 2343 996 w
-(floating-point) 2826 996 w
-(and) 3552 996 w
-(CAS) 3776 996 w
-(\(compare-and-swap\)) 4012 996 w
-(instructions.) 720 1116 w
-(It) 1413 1116 w
-(is) 1534 1116 w
-(known) 1669 1116 w
-(to) 2044 1116 w
-(run) 2197 1116 w
-(on) 2417 1116 w
-(the) 2595 1116 w
-(Sheevaplug) 2805 1116 w
-(and) 3411 1116 w
-(Openrd-client) 3646 1116 w
-(boards.) 4386 1116 w
-(It) 4839 1116 w
-(is) 4960 1116 w
-(derived) 720 1236 w
-(from) 1118 1236 w
-(a) 1388 1236 w
-(port) 1481 1236 w
-(of) 1721 1236 w
-(native) 1857 1236 w
-(Inferno) 2186 1236 w
-(to) 2572 1236 w
-(the) 2708 1236 w
-(Sheevaplug) 2901 1236 w
-(by) 3490 1236 w
-(Salva) 3643 1236 w
-(Peir) 3926 1236 w
- save 4107 1236 m
+(is) 1208 996 w
+(an) 1322 996 w
+(ARM) 1473 996 w
+(kernel) 1725 996 w
+(for) 2061 996 w
+(the) 2234 996 w
+(ARM926EJ-S) 2423 996 w
+(processor) 3061 996 w
+(and) 3571 996 w
+(it) 3785 996 w
+(emulates) 3885 996 w
+(floating-point) 4358 996 w
+(and) 720 1116 w
+(CAS) 946 1116 w
+(\(compare-and-swap\)) 1184 1116 w
+(instructions.) 2258 1116 w
+(It) 2941 1116 w
+(is) 3053 1116 w
+(known) 3178 1116 w
+(to) 3543 1116 w
+(run) 3686 1116 w
+(on) 3896 1116 w
+(the) 4064 1116 w
+(Sheevaplug) 4264 1116 w
+(and) 4860 1116 w
+(Openrd-client) 720 1236 w
+(boards.) 1446 1236 w
+(It) 1885 1236 w
+(is) 1993 1236 w
+(derived) 2115 1236 w
+(from) 2517 1236 w
+(a) 2791 1236 w
+(port) 2888 1236 w
+(of) 3132 1236 w
+(native) 3272 1236 w
+(Inferno) 3605 1236 w
+(to) 3995 1236 w
+(the) 4135 1236 w
+(Sheevaplug) 4332 1236 w
+(by) 4925 1236 w
+(Salva) 720 1356 w
+(Peir) 1004 1356 w
+ save 1185 1356 m
  currentpoint translate 10 10 scale ptsize dup scale
  16 24 true [16 0 0 -16 0 22]
  {<0000000000000000000000000000000004001c00380040003c006600c300c300c300c300c30066003c00000000000000>}
  imagemask restore
-(and) 4207 1236 w
-(Mechiel) 4426 1236 w
-(Luk\255) 4834 1236 w
-(kien.) 720 1356 w
-(There) 1049 1356 w
-(are) 1387 1356 w
-(many) 1599 1356 w
-(features) 1920 1356 w
-(of) 2374 1356 w
-(the) 2531 1356 w
-(Kirkwood) 2745 1356 w
-(system-on-a-chip) 3259 1356 w
-(that) 4215 1356 w
-(it) 4465 1356 w
-(does) 4590 1356 w
-(not) 4880 1356 w
-(exploit.) 720 1476 w
-(Initially,) 1155 1476 w
-(there) 1573 1476 w
-(are) 1860 1476 w
-(drivers) 2047 1476 w
-(for) 2415 1476 w
-(up) 2589 1476 w
-(to) 2749 1476 w
-(two) 2882 1476 w
-(Gigabit) 3092 1476 w
-(Ethernet) 3474 1476 w
-(interfaces) 3915 1476 w
-(and) 4426 1476 w
-(the) 4642 1476 w
-(con\255) 4833 1476 w
-(sole) 720 1596 w
-(serial) 950 1596 w
-(port; we hope to add USB and flash memory drivers, and a video driver for the) 1244 1596 w
-(Openrd-client.) 720 1716 w
+(and) 1285 1356 w
+(Mechiel) 1504 1356 w
+(Lukkien.) 1912 1356 w
+(There) 2393 1356 w
+(are) 2710 1356 w
+(many) 2901 1356 w
+(features) 3202 1356 w
+(of) 3636 1356 w
+(the) 3773 1356 w
+(Kirkwood) 3967 1356 w
+(system-on-) 4461 1356 w
+(a-chip) 720 1476 w
+(that) 1077 1476 w
+(it) 1307 1476 w
+(does) 1412 1476 w
+(not) 1682 1476 w
+(exploit.) 1881 1476 w
+(There) 2320 1476 w
+(are) 2637 1476 w
+(currently) 2829 1476 w
+(drivers) 3300 1476 w
+(for) 3673 1476 w
+(up) 3852 1476 w
+(to) 4017 1476 w
+(two) 4155 1476 w
+(Gigabit) 4370 1476 w
+(Ether\255) 4757 1476 w
+(net) 720 1596 w
+(interfaces,) 913 1596 w
+(USB) 1458 1596 w
+(and) 1677 1596 w
+(the) 1895 1596 w
+(console) 2087 1596 w
+(serial) 2495 1596 w
+(port;) 2793 1596 w
+(we) 3064 1596 w
+(hope) 3234 1596 w
+(to) 3513 1596 w
+(add) 3648 1596 w
+(crypto) 3866 1596 w
+(acceleration,) 4208 1596 w
+(and) 4860 1596 w
+(a video driver for the Openrd-client.) 720 1716 w
 10 /LucidaSans-Demi f
 (The TI OMAP3530 operating system) 720 1956 w
 10 /LucidaSansUnicode00 f
 (This) 970 2112 w
-(is) 1215 2112 w
-(an) 1335 2112 w
-(ARM) 1492 2112 w
-(kernel) 1750 2112 w
-(that) 2092 2112 w
-(emulates) 2324 2112 w
-(pre-VFPv3) 2804 2112 w
-(floating-point) 3352 2112 w
-(and) 4075 2112 w
-(CAS) 4296 2112 w
-(\(compare-) 4529 2112 w
-(and-swap\)) 720 2232 w
-(instructions.) 1283 2232 w
-(It) 1966 2232 w
-(is) 2078 2232 w
-(known) 2204 2232 w
-(to) 2570 2232 w
-(run) 2714 2232 w
-(on) 2925 2232 w
-(the) 3094 2232 w
-(IGEPv2) 3295 2232 w
-(board) 3666 2232 w
-(and) 3995 2232 w
-(might) 4221 2232 w
-(eventually) 4550 2232 w
-(run) 720 2352 w
-(on) 933 2352 w
-(the) 1104 2352 w
-(Beagleboard,) 1307 2352 w
-(once) 1986 2352 w
-(USB) 2264 2352 w
-(is) 2493 2352 w
-(working.) 2621 2352 w
-(There) 3123 2352 w
-(are) 3449 2352 w
-(many) 3649 2352 w
-(features) 3959 2352 w
-(of) 4402 2352 w
-(the) 4548 2352 w
-(OMAP) 4752 2352 w
-(system-on-a-chip) 720 2472 w
-(that) 1664 2472 w
-(it) 1901 2472 w
-(does) 2013 2472 w
-(not) 2290 2472 w
-(exploit.) 2496 2472 w
-(Initially,) 2942 2472 w
-(there) 3371 2472 w
-(are) 3669 2472 w
-(drivers) 3867 2472 w
-(for) 4246 2472 w
-(the) 4431 2472 w
-(IGEPv2) 4632 2472 w
+(is) 1230 2112 w
+(an) 1366 2112 w
+(ARM) 1539 2112 w
+(kernel) 1813 2112 w
+(for) 2171 2112 w
+(the) 2366 2112 w
+(Cortex-A8) 2577 2112 w
+(processor) 3148 2112 w
+(and) 3680 2112 w
+(it) 3916 2112 w
+(emulates) 4038 2112 w
+(pre-VFPv3) 4533 2112 w
+(floating-point) 720 2232 w
+(and) 1456 2232 w
+(CAS) 1690 2232 w
+(\(compare-and-swap\)) 1936 2232 w
+(instructions.) 3018 2232 w
+(It) 3709 2232 w
+(is) 3829 2232 w
+(known) 3963 2232 w
+(to) 4337 2232 w
+(run) 4489 2232 w
+(on) 4708 2232 w
+(the) 4885 2232 w
+(IGEPv2) 720 2352 w
+(board) 1080 2352 w
+(and) 1398 2352 w
+(might) 1613 2352 w
+(eventually) 1931 2352 w
+(run) 2456 2352 w
+(on) 2656 2352 w
+(the) 2815 2352 w
+(Beagleboard,) 3006 2352 w
+(once) 3673 2352 w
+(USB) 3939 2352 w
+(is) 4156 2352 w
+(working.) 4272 2352 w
+(There) 4762 2352 w
+(are) 720 2472 w
+(many) 926 2472 w
+(features) 1242 2472 w
+(of) 1691 2472 w
+(the) 1843 2472 w
+(OMAP) 2052 2472 w
+(system-on-a-chip) 2394 2472 w
+(that) 3344 2472 w
+(it) 3588 2472 w
+(does) 3707 2472 w
+(not) 3991 2472 w
+(exploit.) 4204 2472 w
+(Initially,) 4657 2472 w
+(there) 720 2592 w
+(are) 1014 2592 w
+(drivers) 1208 2592 w
+(for) 1583 2592 w
+(the) 1764 2592 w
+(IGEPv2) 1961 2592 w
 10 /LucidaSansUnicode20 f
-(\031) 4957 2472 w
+(\031) 2286 2592 w
 10 /LucidaSansUnicode00 f
-(s) 4989 2472 w
-(SMSC) 720 2592 w
-(9221) 1016 2592 w
-(100Mb/s) 1301 2592 w
-(Ethernet) 1776 2592 w
-(interface) 2214 2592 w
-(and) 2671 2592 w
-(the) 2884 2592 w
-(console) 3072 2592 w
-(serial) 3476 2592 w
-(port;) 3770 2592 w
-(we) 4037 2592 w
-(hope) 4204 2592 w
-(to) 4480 2592 w
-(add) 4612 2592 w
-(USB,) 4827 2592 w
-(flash memory and video drivers.) 720 2712 w
+(s) 2318 2592 w
+(SMSC) 2411 2592 w
+(9221) 2717 2592 w
+(100Mb/s) 3012 2592 w
+(Ethernet) 3497 2592 w
+(interface) 3945 2592 w
+(and) 4412 2592 w
+(the) 4635 2592 w
+(con\255) 4833 2592 w
+(sole serial port; we hope to add USB, flash memory and video drivers.) 720 2712 w
 cleartomark
 showpage
 saveobj restore

+ 18 - 6
sys/man/8/booting

@@ -130,13 +130,13 @@ the file name can be omitted and the
 file specified by the
 .B bootf
 parameter for the machine in
-.BR /lib/ndb
+.B /lib/ndb
 will be downloaded by default.
 .PP
 Once the kernel is loaded,
 it prompts for the Ethernet
 protocol to use to reach the root file server; request the default.
-.SS Marvell Kirkwood boards
+.SS Marvell Kirkwood CPU Server
 Supported boards include the Sheevaplug and Openrd-client.
 In the following,
 replace
@@ -167,7 +167,13 @@ saveenv
 .PP
 Thereafter, the board will automatically boot via BOOTP and TFTP
 when reset or turned on.
-.SS TI OMAP3530 boards
+The
+.L bootf
+parameter for
+.B /lib/ndb
+is
+.BR /arm/9plug .
+.SS TI OMAP3530 CPU Server
 Supported boards include the IGEPv2 from ISEE.
 In the following,
 replace
@@ -187,11 +193,11 @@ and edit
 .L /cfg/pxe/\fIMAC
 to taste.
 Be sure to edit the line for
-.L ethaddr
-to
+.L ether0
+to set
 .IP
 .EX
-ethaddr=\fIMAC
+ea=\fIMAC
 .EE
 .PP
 Type this at U-boot once:
@@ -206,6 +212,12 @@ saveenv
 .PP
 Thereafter, the board will automatically boot via BOOTP and TFTP
 when reset or turned on.
+The
+.L bootf
+parameter for
+.B /lib/ndb
+is
+.BR /arm/9beagle .
 .SH "SEE ALSO"
 .IR 9load (8),
 .IR boot (8),

+ 1 - 3
sys/src/9/kw/mkfile

@@ -90,9 +90,7 @@ $OBJ: $HFILES
 install:V: /$objtype/$p$CONF
 
 /$objtype/$p$CONF:D: $p$CONF s$p$CONF
-	{ cp -x $p$CONF s$p$CONF /$objtype } &
-	{ 9fs lookout &&  cp -x $p$CONF s$p$CONF /n/lookout/$objtype } &
-#	{ 9fs piestand && cp -x $p$CONF s$p$CONF /n/piestand/$objtype } &
+	cp -x $p$CONF s$p$CONF /$objtype &
 	wait
 	touch $target
 

+ 1150 - 0
sys/src/9/omap/archomap.c

@@ -0,0 +1,1150 @@
+/*
+ * omap3530 SoC (e.g. beagleboard) architecture-specific stuff
+ *
+ * errata: usb port 3 cannot operate in ulpi mode, only serial or
+ * ulpi tll mode
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+#include "arm.h"
+
+#include "../port/netif.h"
+#include "etherif.h"
+// #include "../port/flashif.h"
+#include "usbehci.h"
+
+typedef struct L3protreg L3protreg;
+typedef struct L3regs L3regs;
+typedef struct Usbotg Usbotg;
+typedef struct Usbtll Usbtll;
+
+/* omap3 non-standard usb stuff */
+struct Usbotg {
+	uchar	faddr;
+	uchar	power;
+	ushort	intrtx;
+	ushort	intrrx;
+	ushort	intrtxe;
+	ushort	intrrxe;
+	uchar	intrusb;
+	uchar	intrusbe;
+	ushort	frame;
+	uchar	index;
+	uchar	testmode;
+
+	/* indexed registers follow; ignore for now */
+	uchar	_pad0[0x400 - 0x10];
+
+	ulong	otgrev;
+	ulong	otgsyscfg;
+	ulong	otgsyssts;
+	ulong	otgifcsel;	/* interface selection */
+	uchar	_pad1[0x414 - 0x410];
+	ulong	otgforcestdby;
+};
+
+enum {
+	/* power bits */
+	Hsen		= 1<<5,		/* high-speed enable */
+
+	/* testmode bits */
+	Forcehost	= 1<<7,		/* force host (vs peripheral) mode */
+	Forcehs		= 1<<4,		/* force high-speed at reset */
+
+	/* otgsyscfg bits */
+	Midle		= 1<<12,	/* no standby mode */
+	Sidle		= 1<<3,		/* no idle mode */
+//	Softreset	= 1<<1,
+
+	/* otgsyssts bits, per sysstatus */
+};
+
+struct Usbtll {
+	ulong	revision;	/* ro */
+	uchar	_pad0[0x10-0x4];
+	ulong	sysconfig;
+	ulong	sysstatus;	/* ro */
+
+	ulong	irqstatus;
+	ulong	irqenable;
+};
+
+enum {
+	/* sysconfig bits */
+	Softreset	= 1<<1,
+
+	/* sysstatus bits */
+	Resetdone	= 1<<0,
+	/* only in uhh->sysstatus */
+	Ehci_resetdone	= 1<<2,
+	Ohci_resetdone	= 1<<1,
+};
+
+struct L3protreg {		/* an L3 protection region */
+	uvlong	req_info_perm;
+	uvlong	read_perm;
+	uvlong	write_perm;
+	uvlong	addr_match;	/* ro? write this one last, then flush */
+};
+
+// TODO: set these permission bits (e.g., for usb)?
+enum {
+	Permusbhost	= 1<<9,
+	Permusbotg	= 1<<4,
+	Permsysdma	= 1<<3,
+	Permmpu		= 1<<1,
+};
+
+struct L3regs {
+	L3protreg *base;
+	int	upper;
+	char	*name;
+};
+L3regs l3regs[] = {
+	(L3regs *)0x68010048, 1, "rt",		/* l3 config */
+	(L3regs *)0x68012448, 7, "gpmc",
+	(L3regs *)0x68012848, 7, "ocm ram",
+	(L3regs *)0x68012c48, 1, "ocm rom",
+	(L3regs *)0x68013048, 7, "mad2d",	/* die-to-die */
+	(L3regs *)0x68014048, 3, "iva2.2",	/* a/v */
+};
+
+/*
+ * PRM_CLKSEL (0x48306d40) low 3 bits are system clock speed, assuming
+ * 	units of MHz: 0 = 12, 1 = 13, 2 = 19.2, 3 = 26, 4 = 38.4, 5 = 16.8
+ */
+
+typedef struct Cm Cm;
+struct Cm {				/* clock management */
+	ulong	fclken;			/* ``functional'' clock enable */
+	ulong	fclken2;
+	ulong	fclken3;
+	uchar	_pad0[0x10 - 0xc];
+
+	ulong	iclken;			/* ``interface'' clock enable */
+	ulong	iclken2;
+	ulong	iclken3;
+	uchar	_pad1[0x20 - 0x1c];
+
+	ulong	idlest;			/* idle status */
+	ulong	idlest2;
+	ulong	idlest3;
+	uchar	_pad2[0x30 - 0x2c];
+
+	ulong	autoidle;
+	ulong	autoidle2;
+	ulong	autoidle3;
+	uchar	_pad3[0x40 - 0x3c];
+
+	union {
+		ulong	clksel[5];
+		struct unused {
+			ulong	sleepdep;
+			ulong	clkstctrl;
+			ulong	clkstst;
+		};
+		uchar	_pad4[0x70 - 0x40];
+	};
+	ulong	clkoutctrl;
+};
+
+typedef struct Prm Prm;
+struct Prm {				/* power & reset management */
+	uchar	_pad[0x50];
+	ulong	rstctrl;
+};
+
+enum {
+	/* clock enable & idle status bits */
+	Wkusimocp	= 1 << 9,	/* SIM card: uses 120MHz clock */
+	Wkwdt2		= 1 << 5,	/* wdt2 clock enable bit for wakeup */
+	Wkgpt1		= 1 << 0,	/* gpt1 clock enable bit for wakeup */
+
+	Pergpio6	= 1 << 17,
+	Pergpio5	= 1 << 16,
+	Pergpio4	= 1 << 15,
+	Pergpio3	= 1 << 14,
+	Pergpio2	= 1 << 13,
+	Perwdt3		= 1 << 12,	/* wdt3 clock enable bit for periphs */
+	Peruart3	= 1 << 11,
+	Pergpt9		= 1 << 10,
+	Pergpt8		= 1 << 9,
+	Pergpt7		= 1 << 8,
+	Pergpt6		= 1 << 7,
+	Pergpt5		= 1 << 6,
+	Pergpt4		= 1 << 5,
+	Pergpt3		= 1 << 4,
+	Pergpt2		= 1 << 3,	/* gpt2 clock enable bit for periphs */
+
+	Usbhost2	= 1 << 1,	/* 120MHz clock enable */
+	Usbhost1	= 1 << 0,	/* 48MHz clock enable */
+	Usbhost		= Usbhost1,	/* iclock enable */
+	Usbhostidle	= 1 << 1,
+	Usbhoststdby	= 1 << 0,
+
+	Coreusbhsotg	= 1 << 4,	/* usb hs otg enable bit */
+	Core3usbtll	= 1 << 2,	/* usb tll enable bit */
+
+	/* core->idlest bits */
+	Coreusbhsotgidle = 1 << 5,
+	Coreusbhsotgstdby = 1 << 4,
+};
+
+typedef struct Gen Gen;
+struct Gen {
+	ulong	padconf_off;
+	ulong	devconf0;
+	uchar	_pad0[0x68 - 8];
+	ulong	devconf1;
+};
+
+static char *
+devidstr(ulong)
+{
+	return "ARM Cortex-A8";
+}
+
+void
+archomaplink(void)
+{
+}
+
+int
+ispow2(uvlong ul)
+{
+	/* see Hacker's Delight if this isn't obvious */
+	return (ul & (ul - 1)) == 0;
+}
+
+/*
+ * return exponent of smallest power of 2 ≥ n
+ */
+int
+log2(ulong n)
+{
+	int i;
+
+	i = 31 - clz(n);
+	if (!ispow2(n))
+		i++;
+	return i;
+}
+
+void
+archconfinit(void)
+{
+	char *p;
+
+	assert(m != nil);
+	p = getconf("*cpumhz");
+	if (p)
+		m->cpuhz = atoi(p) * 1000 * 1000;
+	else
+		m->cpuhz = 500 * 1000 * 1000;	/* beagle speed */
+	m->delayloop = m->cpuhz/2000;		/* initial estimate */
+}
+
+/*
+ * dump the l3 interconnect firewall settings by protection region.
+ * mpu, sys dma and both usbs (0x21a) should be set in all read & write
+ * permission registers.
+ */
+static void
+dumpl3pr(void)
+{
+	int r, level, size, addrspace;
+	uvlong am, base;
+	L3regs *reg;
+	L3protreg *pr;
+
+	for (reg = l3regs; reg < l3regs + nelem(l3regs); reg++) {
+		print("%#p (%s) enabled regions:\n", reg->base, reg->name);
+		for (r = 0; r <= reg->upper; r++) {
+			pr = reg->base + r;
+			if (r == 0)
+				am = 0;
+			else
+				am = pr->addr_match;
+			size = (am >> 3) & MASK(5);
+			if (r > 0 && size == 0)		/* disabled? */
+				continue;
+			print("%d: req %#llux ", r, pr->req_info_perm);
+			if (pr->read_perm == pr->write_perm &&
+			    pr->read_perm == MASK(16))
+				print("rw all");
+			else
+				print("read %#llux write %#llux",
+					pr->read_perm, pr->write_perm);
+			if (r == 0)
+				print(" all addrs, level 0");
+			else {
+				size = 1 << size;	/* 2^size */
+				level = (am >> 9) & 1;
+				if (r == 1)
+					level = 3;
+				else
+					level++;
+				addrspace = am & 7;
+				base = am & ~MASK(10);
+				print(" base %#llux size %dKB level %d addrspace %d",
+					base, size, level, addrspace);
+			}
+			print("\n");
+		}
+	}
+}
+
+static void
+p16(uchar *p, ulong v)
+{
+	*p++ = v>>8;
+	*p   = v;
+}
+
+static void
+p32(uchar *p, ulong v)
+{
+	*p++ = v>>24;
+	*p++ = v>>16;
+	*p++ = v>>8;
+	*p   = v;
+}
+
+int
+archether(unsigned ctlrno, Ether *ether)
+{
+	switch(ctlrno) {
+	case 0:
+		/* there's no built-in ether on the beagle but igepv2 has 1 */
+		ether->type = "9221";
+		ether->ctlrno = ctlrno;
+		ether->irq = 34;
+		ether->nopt = 0;
+		ether->mbps = 100;
+		return 1;
+	}
+	return -1;
+}
+
+#define FREQSEL(x) ((x) << 4)
+
+enum {
+	Dplllock	= 7,
+
+	/* mpu->idlest2 bits */
+	Dplllocked	= 1,
+	Dpllbypassed	= 0,
+};
+
+typedef struct Cntrl Cntrl;
+struct Cntrl {
+	ulong	_pad0;
+	ulong	id;
+	ulong	_pad1;
+	ulong	skuid;
+};
+
+/*
+ * turn on all the necessary clocks on the SoC.
+ *
+ * a ``functional'' clock drives a device; an ``interface'' clock drives
+ * its communication with the rest of the system.  so the interface
+ * clock must be enabled to reach the device's registers.
+ *
+ * dplls: 1 mpu, 2 iva2, 3 core, 4 per, 5 per2.
+ */
+
+static void
+configmpu(void)
+{
+	ulong clk, mhz, nmhz, maxmhz;
+	Cm *mpu = (Cm *)PHYSSCMMPU;
+	Cntrl *id = (Cntrl *)PHYSCNTRL;
+
+	if ((id->skuid & MASK(4)) == 8)
+		maxmhz = 720;
+	else
+		maxmhz = 600;
+	iprint("cpu capable of %ldMHz operation", maxmhz);
+
+	clk = mpu->clksel[0];
+	mhz = (clk >> 8) & MASK(11);		/* configured speed */
+//	iprint("\tfclk src %ld; dpll1 mult %ld (MHz) div %ld",
+//		(clk >> 19) & MASK(3), mhz, clk & MASK(7));
+	iprint("; at %ldMHz", mhz);
+	nmhz = m->cpuhz / (1000*1000);		/* nominal speed */
+	if (mhz == nmhz) {
+		iprint("\n");
+		return;
+	}
+
+	mhz = nmhz;
+	if (mhz > maxmhz) {
+		mhz = maxmhz;
+		iprint("; limiting operation to %ldMHz", mhz);
+	}
+
+	/* disable dpll1 lock mode; put into low-power bypass mode */
+	mpu->fclken2 = mpu->fclken2 & ~MASK(3) | 5;
+	coherence();
+	while (mpu->idlest2 != Dpllbypassed)
+		;
+
+	/*
+	 * there's a dance to change processor speed,
+	 * prescribed in spruf98d §4.7.6.9.
+	 */
+
+	/* just change multiplier; leave divider alone at 12 (meaning 13?) */
+	mpu->clksel[0] = clk & ~(MASK(11) << 8) | mhz << 8;
+	coherence();
+
+	/* set output divider (M2) in clksel[1]: leave at 1 */
+
+	/*
+	 * u-boot calls us with just freqsel 3 (~1MHz) & dpll1 lock mode.
+	 */
+	/* set FREQSEL */
+	mpu->fclken2 = mpu->fclken2 & ~FREQSEL(MASK(4)) | FREQSEL(3);
+	coherence();
+
+	/* set ramp-up delay to `fast' */
+	mpu->fclken2 = mpu->fclken2 & ~(MASK(2) << 8) | 3 << 8;
+	coherence();
+
+	/* set auto-recalibration (off) */
+	mpu->fclken2 &= ~(1 << 3);
+	coherence();
+
+	/* disable auto-idle: ? */
+	/* unmask clock intr: later */
+
+	/* enable dpll lock mode */
+	mpu->fclken2 |= Dplllock;
+	coherence();
+	while (mpu->idlest2 != Dplllocked)
+		;
+	delay(200);			/* allow time for speed to ramp up */
+
+	if (((mpu->clksel[0] >> 8) & MASK(11)) != mhz)
+		panic("mpu clock speed change didn't stick");
+	iprint("; now at %ldMHz\n", mhz);
+}
+
+static void
+configpll(void)
+{
+	int i;
+	Cm *pll = (Cm *)PHYSSCMPLL;
+
+	pll->clkoutctrl |= 1 << 7;	/* enable sys_clkout2 */
+	coherence();
+	delay(10);
+
+	/*
+	 * u-boot calls us with just freqsel 3 (~1MHz) & lock mode
+	 * for both dplls (3 & 4).  ensure that.
+	 */
+	if ((pll->idlest & 3) != 3) {
+		/* put dpll[34] into low-power bypass mode */
+		pll->fclken = pll->fclken & ~(MASK(3) << 16 | MASK(3)) |
+			1 << 16 | 5;
+		coherence();
+		while (pll->idlest & 3)  /* wait for both to bypass or stop */
+			;
+
+		pll->fclken =  (FREQSEL(3) | Dplllock) << 16 |
+				FREQSEL(3) | Dplllock;
+		coherence();
+		while ((pll->idlest & 3) != 3)	/* wait for both to lock */
+			;
+	}
+
+	/*
+	 * u-boot calls us with just freqsel 1 (default but undefined)
+	 * & stop mode for dpll5.  try to lock it at 120MHz.
+	 */
+	if (!(pll->idlest2 & Dplllocked)) {
+		/* force dpll5 into low-power bypass mode */
+		pll->fclken2 = 3 << 8 | FREQSEL(1) | 1;
+		coherence();
+		for (i = 0; pll->idlest2 & Dplllocked && i < 20; i++)
+			delay(50);
+		if (i >= 20)
+			iprint(" [dpll5 failed to stop]");
+
+		/*
+		 * CORE_CLK is 26MHz.
+		 */
+		pll->clksel[4-1] = 120 << 8 | 12;	/* M=120, N=12+1 */
+		/* M2 divisor: 120MHz clock is exactly the DPLL5 clock */
+		pll->clksel[5-1] = 1;
+		coherence();
+
+		pll->fclken2 = 3 << 8 | FREQSEL(1) | Dplllock; /* def. freq */
+		coherence();
+
+		for (i = 0; !(pll->idlest2 & Dplllocked) && i < 20; i++)
+			delay(50);
+		if (i >= 20)
+			iprint(" [dpll5 failed to lock]");
+	}
+	if (!(pll->idlest2 & (1<<1)))
+		iprint(" [no 120MHz clock]");
+	if (!(pll->idlest2 & (1<<3)))
+		iprint(" [no dpll5 120MHz clock output]");
+}
+
+static void
+configper(void)
+{
+	Cm *per = (Cm *)PHYSSCMPER;
+
+	per->clksel[0] &= ~MASK(8);	/* select 32kHz clock for GPTIMER2-9 */
+
+	per->iclken |= Pergpio6 | Pergpio5 | Perwdt3 | Pergpt2;
+	coherence();
+	per->fclken |= Pergpio6 | Pergpio5 | Perwdt3 | Pergpt2;
+	coherence();
+	while (per->idlest & (Pergpio6 | Pergpio5 | Perwdt3 | Pergpt2))
+		;
+}
+
+static void
+configwkup(void)
+{
+	Cm *wkup = (Cm *)PHYSSCMWKUP;
+
+	/* select 32kHz clock (not system clock) for GPTIMER1 */
+	wkup->clksel[0] &= ~1;
+
+	wkup->iclken |= Wkusimocp | Wkwdt2 | Wkgpt1;
+	coherence();
+	wkup->fclken |= Wkusimocp | Wkwdt2 | Wkgpt1;
+	coherence();
+	while (wkup->idlest & (Wkusimocp | Wkwdt2 | Wkgpt1))
+		;
+}
+
+static void
+configusb(void)
+{
+	int i;
+	Cm *usb = (Cm *)PHYSSCMUSB;
+
+	/*
+	 * make the usb registers accessible without address faults,
+	 * notably uhh, ochi & ehci.  tll seems to be separate & otg is okay.
+	 */
+	usb->iclken |= Usbhost;
+	coherence();
+	usb->fclken |= Usbhost1 | Usbhost2;	/* includes 120MHz clock */
+	coherence();
+	for (i = 0; usb->idlest & Usbhostidle && i < 20; i++)
+		delay(50);
+	if (i >= 20)
+		iprint(" [usb inaccessible]");
+}
+
+static void
+configcore(void)
+{
+	Cm *core = (Cm *)PHYSSCMCORE;
+
+	/*
+	 * make the usb tll registers accessible.
+	 */
+	core->iclken  |= Coreusbhsotg;
+	core->iclken3 |= Core3usbtll;
+	coherence();
+	core->fclken3 |= Core3usbtll;
+	coherence();
+	delay(100);
+	while (core->idlest & Coreusbhsotgidle)
+		;
+	if (core->idlest3 & Core3usbtll)
+		iprint(" [no usb tll]");
+}
+
+static void
+configclks(void)
+{
+	int s;
+	Gen *gen = (Gen *)PHYSSCMPCONF;
+
+	delay(20);
+	s = splhi();
+	configmpu();		/* sets cpu clock rate, turns on dplls 1 & 2 */
+
+	/*
+	 * the main goal is to get enough clocks running, in the right order,
+	 * so that usb has all the necessary clock signals.
+	 */
+	iprint("clocks:");
+	iprint(" usb");
+	configusb();		/* starts usb clocks & 120MHz clock */
+	iprint(", pll");
+	configpll();		/* starts dplls 3, 4 & 5 & 120MHz clock */
+	iprint(", wakeup");
+	configwkup();		/* starts timer clocks and usim clock */
+	iprint(", per");
+	configper();		/* starts timer & gpio (ether) clocks */
+	iprint(", core");
+	configcore();		/* starts usb tll */
+	iprint("\n");
+
+	gen->devconf0 |= 1 << 1 | 1 << 0;	/* dmareq[01] edge sensitive */
+	/* make dmareq[2-6] edge sensitive */
+	gen->devconf1 |= 1 << 23 | 1 << 22 | 1 << 21 | 1 << 8 | 1 << 7;
+	coherence();
+	splx(s);
+	delay(20);
+}
+
+typedef struct Gpio Gpio;
+struct Gpio {
+	ulong	_pad0[4];
+	ulong	sysconfig;
+	ulong	sysstatus;
+
+	ulong	irqsts1;		/* for mpu */
+	ulong	irqen1;
+	ulong	wkupen;
+	ulong	_pad1;
+	ulong	irqsts2;		/* for iva */
+	ulong	irqen2;
+
+	ulong	ctrl;
+
+	ulong	oe;
+	ulong	datain;
+	ulong	dataout;
+
+	ulong	lvldet0;
+	ulong	lvldet1;
+	ulong	risingdet;
+	ulong	fallingdet;
+
+	/* rest are uninteresting */
+	ulong	deben;			/* debouncing enable */
+	ulong	debtime;
+	ulong	_pad2[2];
+
+	ulong	clrirqen1;
+	ulong	setirqen1;
+	ulong	_pad3[2];
+
+	ulong	clrirqen2;
+	ulong	setirqen2;
+	ulong	_pad4[2];
+
+	ulong	clrwkupen;
+	ulong	setwkupen;
+	ulong	_pad5[2];
+
+	ulong	clrdataout;
+	ulong	setdataout;
+};
+
+/* see ether9221.c for explanation */
+enum {
+	Ethergpio	= 176,
+	Etherchanbit	= 1 << (Ethergpio % 32),
+};
+
+static void
+resetwait(ulong *reg)
+{
+	long bound;
+
+	for (bound = 400*1000*1000; !(*reg & Resetdone) && bound > 0; bound--)
+		;
+	if (bound <= 0)
+		iprint("archomap: Resetdone didn't come ready\n");
+}
+
+/*
+ * gpio irq 1 goes to the mpu intr ctlr; irq 2 goes to the iva's.
+ * this stuff is magic and without it, we won't get irq 34 interrupts
+ * from the 9221 ethernet controller.
+ */
+static void
+configgpio(void)
+{
+	Gpio *gpio = (Gpio *)PHYSGPIO6;
+
+	gpio->sysconfig = Softreset;
+	coherence();
+	resetwait(&gpio->sysstatus);
+
+	gpio->ctrl = 1<<1 | 0;	/* enable this gpio module, gating ratio 1 */
+	gpio->oe |= Etherchanbit;	/* cfg ether pin as input */
+	coherence();
+
+	gpio->irqen1 = Etherchanbit;	/* channel # == pin # */
+	gpio->irqen2 = 0;
+
+	gpio->lvldet0 = Etherchanbit;	/* enable irq ass'n on low det'n */
+	gpio->lvldet1 = 0;		/* disable irq ass'n on high det'n */
+	gpio->risingdet = 0;		/* enable irq rising edge det'n */
+	gpio->fallingdet = 0;		/* disable irq falling edge det'n */
+
+	gpio->wkupen = 0;
+
+	gpio->deben = 0;		/* no de-bouncing */
+	gpio->debtime = 0;
+	coherence();
+
+	gpio->irqsts1 = ~0;		/* dismiss all outstanding intrs */
+	gpio->irqsts2 = ~0;
+	coherence();
+}
+
+void
+gpioirqclr(void)
+{
+	Gpio *gpio = (Gpio *)PHYSGPIO6;
+
+	gpio->irqsts1 = gpio->irqsts1;
+	coherence();
+}
+
+/*
+ * these shift values are for the Cortex-A8 L1 cache (A=2, L=6) and
+ * the Cortex-A8 L2 cache (A=3, L=6).
+ * A = log2(# of ways), L = log2(bytes per cache line).
+ * see armv7 arch ref p. 1403.
+ *
+ * #define L1WAYSH 30
+ * #define L1SETSH 6
+ * #define L2WAYSH 29
+ * #define L2SETSH 6
+ */
+enum {
+	/*
+	 * cache capabilities.  write-back vs write-through is controlled
+	 * by the Buffered bit in PTEs.
+	 */
+	Cawt	= 1 << 31,
+	Cawb	= 1 << 30,
+	Cara	= 1 << 29,
+	Cawa	= 1 << 28,
+};
+
+static char *
+l1iptype(uint type)
+{
+	static char *types[] = {
+		"reserved",
+		"asid-tagged VIVT",
+		"VIPT",
+		"PIPT",
+	};
+
+	if (type >= nelem(types) || types[type] == nil)
+		return "GOK";
+	return types[type];
+}
+
+void
+cacheinfo(int level, Memcache *cp)
+{
+	ulong setsways;
+
+	/* select cache level */
+	cpwrsc(CpIDcssel, CpID, CpIDid, 0, (level - 1) << 1);
+
+	setsways = cprdsc(CpIDcsize, CpID, CpIDid, 0);
+	cp->l1ip = cprdsc(0, CpID, CpIDidct, CpIDct);
+	cp->level = level;
+	cp->nways = ((setsways >> 3)  & MASK(10)) + 1;
+	cp->nsets = ((setsways >> 13) & MASK(15)) + 1;
+	cp->log2linelen = (setsways & MASK(2)) + 2 + 2;
+	cp->linelen = 1 << cp->log2linelen;
+	cp->setsways = setsways;
+
+	cp->setsh = cp->log2linelen;
+	cp->waysh = 32 - log2(cp->nways);
+}
+
+static void
+prcachecfg(void)
+{
+	int cache;
+	Memcache mc;
+
+	for (cache = 1; cache <= 2; cache++) {
+		cacheinfo(cache, &mc);
+		iprint("l%d: %d ways %d sets %d bytes/line",
+			mc.level, mc.nways, mc.nsets, mc.linelen);
+		if (mc.linelen != CACHELINESZ)
+			iprint(" *should* be %d", CACHELINESZ);
+		if (mc.setsways & Cawt)
+			iprint("; can write-through");
+		if (mc.setsways & Cawb)
+			iprint("; can write-back");
+#ifdef COMPULSIVE			/* both caches can do this */
+		if (mc.setsways & Cara)
+			iprint("; can read-allocate");
+#endif
+		if (mc.setsways & Cawa)
+			iprint("; can write-allocate");
+		if (cache == 1)
+			iprint("; l1 I policy %s",
+				l1iptype((mc.l1ip >> 14) & MASK(2)));
+		iprint("\n");
+	}
+}
+
+enum {
+	/* fp control regs.  most are read-only */
+	Fpsid,
+	Fpscr,			/* rw */
+	Mvfr1	= 6,
+	Mvfr0,
+	Fpexc,			/* rw */
+};
+
+static char *
+subarch(int impl, uint sa)
+{
+	static char *armarchs[] = {
+		"VFPv1 (pre-armv7)",
+		"VFPv2 (pre-armv7)",
+		"VFPv3+ with common VFP subarch v2",
+		"VFPv3+ with null subarch",
+		"VFPv3+ with common VFP subarch v3",
+	};
+
+	if (impl != 'A' || sa >= nelem(armarchs))
+		return "GOK";
+	else
+		return armarchs[sa];
+}
+
+/*
+ * padconf bits in a short, 2 per register
+ *	15	wakeupevent
+ *	14	wakeupenable
+ *	13	offpulltypeselect
+ *	12	offpulludenable
+ *	11	offoutvalue
+ *	10	offoutenable
+ *	9	offenable
+ *	8	inputenable
+ *	4	pulltypeselect
+ *	3	pulludenable
+ *	2-0	muxmode
+ */
+
+enum {
+	Muxmode		= MASK(3),
+};
+
+/* set SCM pad config mux mode */
+void
+setmuxmode(ulong addr, int shorts, int mode)
+{
+	int omode;
+	ushort *ptr;
+
+	for (ptr = (ushort *)addr; shorts-- > 0; ptr++) {
+		omode = *ptr & Muxmode;
+		if (omode != mode) {
+//			print("scm pad %#p was mux mode %d, now %d\n",
+//				ptr, omode, mode);
+//			delay(10);
+			*ptr = *ptr & ~Muxmode | mode & Muxmode;
+		}
+	}
+	coherence();
+}
+
+static void
+setpadmodes(void)
+{
+	ushort *ptr;
+
+	/* set scm pad modes for usb; hasn't made any difference yet */
+	setmuxmode(0x48002166, 7, 5);	/* hsusb3_tll; is mode 4 */
+	setmuxmode(0x48002180, 1, 5);	/* hsusb3_tll_clk; is mode 4 */
+	setmuxmode(0x48002184, 4, 5);	/* hsusb3_tll_data?; is mode 1 */
+	setmuxmode(0x480021a2, 12, 0);	/* hsusb0 (console) */
+	setmuxmode(0x480021d4, 6, 2);	/* hsusb2_tll (ehci port 2); */
+					/* mode 3 is hsusb2 */
+	setmuxmode(0x480025d8, 18, 6);	/* hsusb[12]_tll; mode 3 is */
+					/* hsusb1_data, hsusb2 */
+
+	/*
+	 * igep only: mode 4 of 21d2 is gpio_176 (smsc9221 ether irq).
+	 * see ether9221.c for more.
+	 */
+	ptr = (ushort *)0x480021d2;
+//	setmuxmode((uintptr)ptr, 1, 4);
+	/* input enable, pu/pd = 3, muxmode 4 */
+	*ptr = 1 << 8 | 3 << 3 | 4;
+	coherence();
+}
+
+static char *
+implement(uchar impl)
+{
+	if (impl == 'A')
+		return "arm";
+	else
+		return "unknown";
+}
+
+static void
+fpon(void)
+{
+	int gotfp, impl;
+	ulong acc, scr;
+
+	gotfp = 1 << CpFP | 1 << CpDFP;
+	cpwrsc(0, CpCONTROL, 0, CpCPaccess, MASK(28));
+	acc = cprdsc(0, CpCONTROL, 0, CpCPaccess);
+	if ((acc & (MASK(2) << (2*CpFP))) == 0) {
+		gotfp &= ~(1 << CpFP);
+		print("fpon: no single FP coprocessor\n");
+	}
+	if ((acc & (MASK(2) << (2*CpDFP))) == 0) {
+		gotfp &= ~(1 << CpDFP);
+		print("fpon: no double FP coprocessor\n");
+	}
+	if (!gotfp) {
+		print("fpon: no FP coprocessors\n");
+		return;
+	}
+
+	/* enable fp.  must be first operation on the FPUs. */
+	fpwr(Fpexc, fprd(Fpexc) | 1 << 30);
+
+	scr = fprd(Fpsid);
+	impl = scr >> 24;
+	print("fp: %s arch %s", implement(impl),
+		subarch(impl, (scr >> 16) & MASK(7)));
+
+	scr = fprd(Fpscr);
+	// TODO configure Fpscr further
+	scr |= 1 << 9;					/* div-by-0 exception */
+	scr &= ~(MASK(2) << 20 | MASK(3) << 16);	/* all ops are scalar */
+	fpwr(Fpscr, scr);
+	print("\n");
+	/* we should now be able to execute VFP-style FP instr'ns natively */
+}
+
+/*
+ * set gpmc_config7[i] for all chip selects?
+ *	CSVALID | cs_base | (maskaddress == 010)
+ * addr is PHYSGPMC + 0x78 + 0x30*i, for i from 0 to 7
+ *
+ * there are secure sdrc registers at 0x48002460
+ * sdrc regs at PHYSSDRC; see spruf98c §1.2.8.2.
+ * set or dump l4 prot regs at PHYSL4?
+ */
+void
+archreset(void)
+{
+	int bound;
+	Uhh *uhh;
+	Usbotg *otg;
+	Usbtll *tll;
+	static int beenhere;
+
+	if (beenhere)
+		return;
+	beenhere = 1;
+
+//	dumpl3pr();
+	prcachecfg();
+	/* fight omap35x errata 2.0.1.104 */
+	memset((void *)PHYSSWBOOTCFG, 0, 240);
+	coherence();
+
+	setpadmodes();
+	configclks();
+	configgpio();
+
+	iprint("usb: resetting otg...");
+	otg = (Usbotg *)PHYSUSBOTG;
+	otg->otgsyscfg = Softreset;	/* see omap35x errata 3.1.1.144 */
+	coherence();
+	resetwait(&otg->otgsyssts);
+	otg->otgsyscfg |= Sidle | Midle;
+	coherence();
+
+	iprint("uhh...");
+	uhh = (Uhh *)PHYSUHH;
+	uhh->sysconfig |= Softreset;
+	coherence();
+	resetwait(&uhh->sysstatus);
+	for (bound = 400*1000*1000; !(uhh->sysstatus & Resetdone) && bound > 0;
+	    bound--)
+		;
+	uhh->sysconfig |= Sidle | Midle;
+
+	/*
+	 * using the TLL seems to be an optimisation when talking
+	 * to another identical SoC, thus not very useful, so
+	 * force PHY (ULPI) mode.
+	 */
+	/* this bit is normally off when we get here */
+	uhh->hostconfig &= ~P1ulpi_bypass;
+	coherence();
+	if (uhh->hostconfig & P1ulpi_bypass)
+		iprint("utmi (tll) mode...");	/* via tll */
+	else
+		/* external transceiver (phy), no tll */
+		iprint("ulpi (phy) mode...");
+
+	tll = (Usbtll *)PHYSUSBTLL;
+	if (probeaddr(PHYSUSBTLL) >= 0) {
+		iprint("tll...");
+		tll->sysconfig |= Softreset;
+		coherence();
+		resetwait(&tll->sysstatus);
+		tll->sysconfig |= Sidle;
+		coherence();
+	} else
+		iprint("no tll...");
+	iprint("\n");
+
+	fpon();
+}
+
+enum {
+	Rstgs	= 1 << 1,		/* global sw. reset */
+};
+
+void
+archreboot(void)
+{
+	Prm *prm = (Prm *)PHYSPRMGLBL;
+
+	iprint("archreboot: reset!\n");
+	delay(20);
+
+	prm->rstctrl |= Rstgs;
+	coherence();
+	delay(500);
+
+	/* shouldn't get here */
+	splhi();
+	iprint("waiting");
+	for(;;) {
+		delay(1000);
+		print(".");
+	}
+}
+
+void
+kbdinit(void)
+{
+}
+
+void
+lastresortprint(char *buf, long bp)
+{
+	iprint("%.*s", (int)bp, buf);	/* nothing else seems to work */
+}
+
+static void
+scmdump(ulong addr, int shorts)
+{
+	ushort reg;
+	ushort *ptr;
+
+	ptr = (ushort *)addr;
+	print("scm regs:\n");
+	while (shorts-- > 0) {
+		reg = *ptr++;
+		print("%#p: %#ux\tinputenable %d pulltypeselect %d "
+			"pulludenable %d muxmode %d\n",
+			ptr, reg, (reg>>8) & 1, (reg>>4) & 1, (reg>>3) & 1,
+			reg & 7);
+	}
+}
+
+char *cputype2name(char *buf, int size);
+
+void
+cpuidprint(void)
+{
+	char name[64];
+
+	cputype2name(name, sizeof name);
+	iprint("cpu%d: %lldMHz ARM %s\n", m->machno, m->cpuhz / (1000*1000),
+		name);
+}
+
+static void
+missing(ulong addr, char *name)
+{
+	static int firstmiss = 1;
+
+	if (probeaddr(addr) >= 0)
+		return;
+	if (firstmiss) {
+		iprint("missing:");
+		firstmiss = 0;
+	} else
+		iprint(",\n\t");
+	iprint(" %s at %#lux", name, addr);
+}
+
+/* verify that all the necessary device registers are accessible */
+void
+chkmissing(void)
+{
+	delay(20);
+	missing(PHYSSCM, "scm");
+	missing(KZERO, "dram");
+	missing(PHYSL3, "l3 config");
+	missing(PHYSINTC, "intr ctlr");
+	missing(PHYSTIMER1, "timer1");
+	missing(PHYSCONS, "console uart2");
+	missing(PHYSUART0, "uart0");
+	missing(PHYSUART1, "uart1");
+	missing(PHYSETHER, "smc9221");		/* not on beagle */
+	missing(PHYSUSBOTG, "usb otg");
+	missing(PHYSUHH, "usb uhh");
+	missing(PHYSOHCI, "usb ohci");
+	missing(PHYSEHCI, "usb ehci");
+	missing(PHYSSDMA, "dma");
+	missing(PHYSWDOG, "watchdog timer");
+	missing(PHYSUSBTLL, "usb tll");
+	iprint("\n");
+	delay(20);
+}
+
+#ifdef USE_FLASH
+void
+archflashwp(Flash*, int)
+{
+}
+
+/*
+ * for ../port/devflash.c:/^flashreset
+ * retrieve flash type, virtual base and length and return 0;
+ * return -1 on error (no flash)
+ */
+int
+archflashreset(int bank, Flash *f)
+{
+	if(bank != 0)
+		return -1;
+	f->type = "nand";
+	f->addr = (void*)PHYSNAND;
+	f->size = 0;	/* done by probe */
+	f->width = 1;
+	f->interleave = 0;
+	return 0;
+}
+#endif

+ 246 - 0
sys/src/9/omap/arm.h

@@ -0,0 +1,246 @@
+/*
+ * arm-specific definitions for cortex-a8
+ * these are used in C and assembler
+ *
+ * `cortex' refers specifically to the cortex-a8.
+ */
+
+/*
+ * Program Status Registers
+ */
+#define PsrMusr		0x00000010		/* mode */
+#define PsrMfiq		0x00000011
+#define PsrMirq		0x00000012
+#define PsrMsvc		0x00000013	/* `protected mode for OS' */
+#define PsrMmon		0x00000016	/* `secure monitor' (trustzone hyper) */
+#define PsrMabt		0x00000017
+#define PsrMund		0x0000001B
+#define PsrMsys		0x0000001F	/* `privileged user mode for OS' (trustzone) */
+#define PsrMask		0x0000001F
+
+#define PsrDfiq		0x00000040		/* disable FIQ interrupts */
+#define PsrDirq		0x00000080		/* disable IRQ interrupts */
+
+#define PsrV		0x10000000		/* overflow */
+#define PsrC		0x20000000		/* carry/borrow/extend */
+#define PsrZ		0x40000000		/* zero */
+#define PsrN		0x80000000		/* negative/less than */
+
+/*
+ * Coprocessors
+ */
+#define CpFP		10			/* float FP, VFP cfg. */
+#define CpDFP		11			/* double FP */
+#define CpSC		15			/* System Control */
+
+/*
+ * Primary (CRn) CpSC registers.
+ */
+#define	CpID		0			/* ID and cache type */
+#define	CpCONTROL	1			/* miscellaneous control */
+#define	CpTTB		2			/* Translation Table Base(s) */
+#define	CpDAC		3			/* Domain Access Control */
+#define	CpFSR		5			/* Fault Status */
+#define	CpFAR		6			/* Fault Address */
+#define	CpCACHE		7			/* cache/write buffer control */
+#define	CpTLB		8			/* TLB control */
+#define	CpCLD		9			/* L2 Cache Lockdown, op1==1 */
+#define CpTLD		10			/* TLB Lockdown, with op2 */
+#define CpVECS		12			/* vector bases, op1==0, Crm==0, op2s (cortex) */
+#define	CpPID		13			/* Process ID */
+#define CpDTLB		15			/* TLB, L1 cache stuff (cortex) */
+
+/*
+ * CpTTB op1==0, Crm==0 opcode2 values.
+ */
+#define CpTTB0		0
+#define CpTTB1		1			/* cortex */
+#define CpTTBctl	2			/* cortex */
+
+/*
+ * CpID Secondary (CRm) registers.
+ */
+#define CpIDidct	0
+
+/*
+ * CpID op1==0 opcode2 fields.
+ * the cortex has more op1 codes for cache size, etc.
+ */
+#define CpIDid		0			/* main ID */
+#define CpIDct		1			/* cache type */
+#define CpIDtlb		3			/* tlb type (cortex) */
+#define CpIDmpid	5			/* multiprocessor id (cortex) */
+
+/* CpIDid op1 values */
+#define CpIDcsize	1			/* cache size (cortex) */
+#define CpIDcssel	2			/* cache size select (cortex) */
+
+/*
+ * CpCONTROL op2 codes, op1==0, Crm==0.
+ */
+#define CpMainctl	0
+#define CpAuxctl	1
+#define CpCPaccess	2
+
+/*
+ * CpCONTROL: op1==0, CRm==0, op2==CpMainctl.
+ * main control register.
+ * cortex/armv7 has more ops and CRm values.
+ */
+#define CpCmmu		0x00000001	/* M: MMU enable */
+#define CpCalign	0x00000002	/* A: alignment fault enable */
+#define CpCdcache	0x00000004	/* C: data cache on */
+#define CpCsbo (3<<22|1<<18|1<<16|017<<3)	/* must be 1 (armv7) */
+#define CpCsbz (CpCtre|1<<26|CpCve|1<<15|7<<7)	/* must be 0 (armv7) */
+#define CpCsw		(1<<10)		/* SW: SWP(B) enable (deprecated in v7) */
+#define CpCpredict	0x00000800	/* Z: branch prediction (armv7) */
+#define CpCicache	0x00001000	/* I: instruction cache on */
+#define CpChv		0x00002000	/* V: high vectors */
+#define CpCrr		(1<<14)	/* RR: round robin vs random cache replacement */
+#define CpCha		(1<<17)		/* HA: hw access flag enable */
+#define CpCdz		(1<<19)		/* DZ: divide by zero fault enable */
+#define CpCfi		(1<<21)		/* FI: fast intrs */
+#define CpCve		(1<<24)		/* VE: intr vectors enable */
+#define CpCee		(1<<25)		/* EE: exception endianness */
+#define CpCnmfi		(1<<27)		/* NMFI: non-maskable fast intrs. */
+#define CpCtre		(1<<28)		/* TRE: TEX remap enable */
+#define CpCafe		(1<<29)		/* AFE: access flag (ttb) enable */
+
+/*
+ * CpCONTROL: op1==0, CRm==0, op2==CpAuxctl.
+ * Auxiliary control register on cortex at least.
+ */
+#define CpACcachenopipe		(1<<20)	/* don't pipeline cache maint. */
+#define CpACcp15serial		(1<<18)	/* serialise CP1[45] ops. */
+#define CpACcp15waitidle	(1<<17)	/* CP1[45] wait-on-idle */
+#define CpACcp15pipeflush	(1<<16)	/* CP1[45] flush pipeline */
+#define CpACneonissue1		(1<<12)	/* neon single issue */
+#define CpACldstissue1		(1<<11)	/* force single issue ld, st */
+#define CpACissue1		(1<<10)	/* force single issue */
+#define CpACnobsm		(1<<7)	/* no branch size mispredicts */
+#define CpACibe			(1<<6)	/* cp15 invalidate & btb enable */
+#define CpACl1neon		(1<<5)	/* cache neon (FP) data in L1 cache */
+#define CpACasa			(1<<4)	/* enable speculative accesses */
+#define CpACl1pe		(1<<3)	/* l1 cache parity enable */
+#define CpACl2en		(1<<1)	/* l2 cache enable; default 1 */
+/*
+ * CpCONTROL Secondary (CRm) registers and opcode2 fields.
+ */
+#define CpCONTROLscr	1
+
+#define CpSCRscr	0
+
+/*
+ * CpCACHE Secondary (CRm) registers and opcode2 fields.  op1==0.
+ * In ARM-speak, 'flush' means invalidate and 'clean' means writeback.
+ */
+#define CpCACHEintr	0			/* interrupt (op2==4) */
+#define CpCACHEisi	1			/* inner-sharable I cache (v7) */
+#define CpCACHEpaddr	4			/* 0: phys. addr (cortex) */
+#define CpCACHEinvi	5			/* instruction, branch table */
+#define CpCACHEinvd	6			/* data or unified */
+// #define CpCACHEinvu	7			/* unified (not on cortex) */
+#define CpCACHEva2pa	8			/* va -> pa translation (cortex) */
+#define CpCACHEwb	10			/* writeback */
+#define CpCACHEinvdse	11			/* data or unified by mva */
+#define CpCACHEwbi	14			/* writeback+invalidate */
+
+#define CpCACHEall	0			/* entire (not for invd nor wb(i) on cortex) */
+#define CpCACHEse	1			/* single entry */
+#define CpCACHEsi	2			/* set/index (set/way) */
+#define CpCACHEtest	3			/* test loop */
+#define CpCACHEwait	4			/* wait (prefetch flush on cortex) */
+#define CpCACHEdmbarr	5			/* wb only (cortex) */
+#define CpCACHEflushbtc	6			/* flush branch-target cache (cortex) */
+#define CpCACHEflushbtse 7			/* ⋯ or just one entry in it (cortex) */
+
+/*
+ * CpTLB Secondary (CRm) registers and opcode2 fields.
+ */
+#define CpTLBinvi	5			/* instruction */
+#define CpTLBinvd	6			/* data */
+#define CpTLBinvu	7			/* unified */
+
+#define CpTLBinv	0			/* invalidate all */
+#define CpTLBinvse	1			/* invalidate single entry */
+#define CpTBLasid	2			/* by ASID (cortex) */
+
+/*
+ * CpCLD Secondary (CRm) registers and opcode2 fields for op1==0. (cortex)
+ */
+#define CpCLDena	12			/* enables */
+#define CpCLDcyc	13			/* cycle counter */
+#define CpCLDuser	14			/* user enable */
+
+#define CpCLDenapmnc	0
+#define CpCLDenacyc	1
+
+/*
+ * CpCLD Secondary (CRm) registers and opcode2 fields for op1==1.
+ */
+#define CpCLDl2		0			/* l2 cache */
+
+#define CpCLDl2aux	2			/* auxiliary control */
+
+/*
+ * l2 cache aux. control
+ */
+#define CpCl2ecc	(1<<28)			/* use ecc, not parity */
+#define CpCl2noldforw	(1<<27)			/* no ld forwarding */
+#define CpCl2nowrcomb	(1<<25)			/* no write combining */
+#define CpCl2nowralldel	(1<<24)			/* no write allocate delay */
+#define CpCl2nowrallcomb (1<<23)		/* no write allocate combine */
+#define CpCl2nowralloc	(1<<22)			/* no write allocate */
+#define CpCl2eccparity	(1<<21)			/* enable ecc or parity */
+#define CpCl2inner	(1<<16)			/* inner cacheability */
+/* other bits are tag ram & data ram latencies */
+
+/*
+ * CpTLD Secondary (CRm) registers and opcode2 fields.
+ */
+#define CpTLDlock	0			/* TLB lockdown registers */
+#define CpTLDpreload	1			/* TLB preload */
+
+#define CpTLDi		0			/* TLB instr. lockdown reg. */
+#define CpTLDd		1			/* " data " " */
+
+/*
+ * CpVECS Secondary (CRm) registers and opcode2 fields.
+ */
+#define CpVECSbase	0
+
+#define CpVECSnorm	0			/* (non-)secure base addr */
+#define CpVECSmon	1			/* secure monitor base addr */
+
+/*
+ * MMU page table entries.
+ * Mbz (0x10) bit is implementation-defined and must be 0 on the cortex.
+ */
+#define Mbz		(0<<4)
+#define Fault		0x00000000		/* L[12] pte: unmapped */
+
+#define Coarse		(Mbz|1)			/* L1 */
+#define Section		(Mbz|2)			/* L1 1MB */
+#define Fine		(Mbz|3)			/* L1 */
+
+#define Large		0x00000001		/* L2 64KB */
+#define Small		0x00000002		/* L2 4KB */
+#define Tiny		0x00000003		/* L2 1KB: not in v7 */
+#define Buffered	0x00000004		/* L[12]: write-back not -thru */
+#define Cached		0x00000008		/* L[12] */
+#define Dom0		0
+
+#define Noaccess	0			/* AP, DAC */
+#define Krw		1			/* AP */
+/* armv7 deprecates AP[2] == 1 & AP[1:0] == 2 (Uro), prefers 3 (new in v7) */
+#define Uro		2			/* AP */
+#define Urw		3			/* AP */
+#define Client		1			/* DAC */
+#define Manager		3			/* DAC */
+
+#define AP(n, v)	F((v), ((n)*2)+4, 2)
+#define L1AP(ap)	(AP(3, (ap)))
+#define L2AP(ap)	(AP(0, (ap)))		/* armv7 */
+#define DAC(n, v)	F((v), (n)*2, 2)
+
+#define HVECTORS	0xffff0000

+ 93 - 0
sys/src/9/omap/arm.s

@@ -0,0 +1,93 @@
+/*
+ * omap3530 machine assist, definitions
+ * cortex-a8 processor
+ *
+ * loader uses R11 as scratch.
+ */
+
+#include "mem.h"
+#include "arm.h"
+
+#undef B					/* B is for 'botch' */
+
+#define KADDR(pa)	(KZERO    | ((pa) & ~KSEGM))
+#define PADDR(va)	(PHYSDRAM | ((va) & ~KSEGM))
+
+#define L1X(va)		(((((va))>>20) & 0x0fff)<<2)
+
+#define MACHADDR	(L1-MACHSIZE)
+
+#define PTEDRAM		(Dom0|L1AP(Krw)|Section|Cached|Buffered)
+#define PTEIO		(Dom0|L1AP(Krw)|Section)
+
+#define DOUBLEMAPMBS	256	/* megabytes of low dram to double-map */
+
+/* steps on R0 */
+#define DELAY(label, mloops) \
+	MOVW	$((mloops)*1000000), R0; \
+label: \
+	SUB.S	$1, R0; \
+	BNE	label
+
+/* wave at the user; clobbers R0, R1 & R6; needs R12 (SB) set */
+#define WAVE(c) \
+	BARRIERS; \
+	MOVW	$(c), R1; \
+	MOVW	$PHYSCONS, R6; \
+	MOVW	R1, (R6); \
+	BARRIERS
+
+/*
+ * new instructions
+ */
+
+#define SMC	WORD	$0xe1600070	/* low 4-bits are call # (trustzone) */
+/* flush branch-target cache; zeroes R0 (cortex) */
+#define FLBTC	\
+	MOVW	$0, R0; \
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEflushbtc
+/* flush one entry of the branch-target cache, va in R0 (cortex) */
+#define FLBTSE	\
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEflushbtse
+
+/* arm v7 arch defines these */
+#define WFI	WORD	$0xe320f003	/* wait for interrupt */
+#define DMB	WORD	$0xf57ff05f	/* data mem. barrier; last f = SY */
+#define DSB	WORD	$0xf57ff04f	/* data synch. barrier; last f = SY */
+#define ISB	WORD	$0xf57ff06f	/* instr. sync. barrier; last f = SY */
+#define NOOP	WORD	$0xe320f000
+#define CLZ(s, d) WORD	$(0xe16f0f10 | (d) << 12 | (s))	/* count leading 0s */
+
+/* floating point */
+#define VMRS(fp, cpu) WORD $(0xeef00a10 | (fp)<<16 | (cpu)<<12) /* FP → arm */
+#define VMSR(cpu, fp) WORD $(0xeee00a10 | (fp)<<16 | (cpu)<<12) /* arm → FP */
+
+/*
+ * a popular code sequence used to write a pte for va is:
+ *
+ *	MOVW	R(n), TTB[LnX(va)]
+ *	// clean the cache line
+ *	DSB
+ *	// invalidate tlb entry for va
+ *	FLBTC
+ *	DSB
+ * 	PFF (now ISB)
+ */
+/* zeroes R0 */
+#define	BARRIERS	FLBTC; DSB; ISB
+
+/*
+ * invoked with PTE bits in R2, pa in R3, PTE pointed to by R4.
+ * fill PTE pointed to by R4 and increment R4 past it.
+ * increment R3 by a MB.  clobbers R1.
+ */
+#define FILLPTE() \
+	ORR	R3, R2, R1;			/* pte bits in R2, pa in R3 */ \
+	MOVW	R1, (R4); \
+	ADD	$4, R4;				/* bump PTE address */ \
+	ADD	$MiB, R3;			/* bump pa */ \
+
+/* zero PTE pointed to by R4 and increment R4 past it. assumes R0 is 0. */
+#define ZEROPTE() \
+	MOVW	R0, (R4); \
+	ADD	$4, R4;				/* bump PTE address */

+ 80 - 0
sys/src/9/omap/beagle

@@ -0,0 +1,80 @@
+# beagle board
+dev
+	root
+	cons
+	env
+	pipe
+	proc
+	mnt
+	srv
+	dup
+	arch
+	ssl
+	tls
+	bridge		log
+	sdp		thwack unthwack
+	cap
+	kprof
+	aoe
+	sd
+#	flash
+#	pnp		pci
+
+	ether		netif
+	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum inferno
+
+##	draw		screen vga vgax
+##	mouse		mouse
+##	vga
+#	kbmap
+##	kbin
+
+	uart
+	usb
+
+link
+	archomap
+	ethermedium
+	loopbackmedium
+	netdevmedium
+
+	ether9221
+# avoid tickling errata 3.1.1.183
+#	usbohci
+	usbehci
+
+ip
+	tcp
+	udp
+	ipifc
+	icmp
+	icmp6
+	ipmux
+	gre
+	esp
+
+misc
+	rdb
+	coproc
+	dma
+	sdaoe		sdscsi
+	softfpu
+	syscall
+	uarti8250
+	ucalloc
+	ucallocb
+##	vgavesa
+
+port
+	int cpuserver = 1;
+	int i8250freq = 3686000;
+
+boot cpu
+	tcp
+
+bootdir
+	boot$CONF.out boot
+	/arm/bin/ip/ipconfig ipconfig
+	/arm/bin/auth/factotum factotum
+	/arm/bin/usb/usbd
+	nvram

+ 247 - 0
sys/src/9/omap/cache.v7.s

@@ -0,0 +1,247 @@
+/*
+ * cortex arm arch v7 cache flushing and invalidation
+ * shared by l.s and rebootcode.s
+ */
+
+TEXT cacheiinv(SB), 1, $-4			/* I invalidate */
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall /* ok on cortex */
+	BARRIERS
+	RET
+
+/*
+ * set/way operators
+ */
+
+TEXT cachedwb_sw(SB), 1, $-4
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEsi
+	RET
+
+TEXT cachedwbinv_sw(SB), 1, $-4
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEsi
+	RET
+
+TEXT cachedinv_sw(SB), 1, $-4
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvd), CpCACHEsi
+	RET
+
+	/* set cache size select */
+TEXT setcachelvl(SB), 1, $-4
+	MCR	CpSC, CpIDcssel, R0, C(CpID), C(CpIDid), 0
+	BARRIERS
+	RET
+
+	/* return cache sizes */
+TEXT getwayssets(SB), 1, $-4
+	MRC	CpSC, CpIDcsize, R0, C(CpID), C(CpIDid), 0
+	RET
+
+/*
+ * l1 cache operations
+ */
+
+TEXT cachedwb(SB), 1, $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedwb_sw(SB), R0
+	MOVW	$1, R8
+	BL	cacheall(SB)
+	MOVW.P	8(R13), R15
+
+TEXT cachedwbinv(SB), 1, $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedwbinv_sw(SB), R0
+	MOVW	$1, R8
+	BL	cacheall(SB)
+	MOVW.P	8(R13), R15
+
+TEXT cachedinv(SB), 1, $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedinv_sw(SB), R0
+	MOVW	$1, R8
+	BL	cacheall(SB)
+	MOVW.P	8(R13), R15
+
+TEXT cacheuwbinv(SB), 1, $-4
+	MOVM.DB.W [R14], (R13)	/* save lr on stack */
+	MOVW	CPSR, R1
+	ORR	$(PsrDirq|PsrDfiq), R1, R0
+	MOVW	R0, CPSR	/* splhi */
+	BARRIERS
+
+	BL	cachedwbinv(SB)
+	BL	cacheiinv(SB)
+
+	MOVW	R1, CPSR
+	BARRIERS
+	MOVM.IA.W (R13), [R14]	/* restore lr */
+	RET
+
+/*
+ * l2 cache operations
+ */
+
+TEXT l2cacheuwb(SB), 1, $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedwb_sw(SB), R0
+	MOVW	$2, R8
+	BL	cacheall(SB)
+	MOVW.P	8(R13), R15
+
+TEXT l2cacheuwbinv(SB), 1, $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	CPSR, R1
+	ORR	$(PsrDirq|PsrDfiq), R1, R0
+	MOVW	R0, CPSR	/* splhi */
+	BARRIERS
+
+	MOVW	$cachedwbinv_sw(SB), R0
+	MOVW	$2, R8
+	BL	cacheall(SB)
+	BL	l2cacheuinv(SB)
+
+	MOVW	R1, CPSR
+	BARRIERS
+	MOVW.P	8(R13), R15
+
+TEXT l2cacheuinv(SB), 1, $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedinv_sw(SB), R0
+	MOVW	$2, R8
+	BL	cacheall(SB)
+	MOVW.P	8(R13), R15
+
+/*
+ * initial translation by 5c, then massaged by hand.
+ */
+
+/*
+ * these shift values are for the Cortex-A8 L1 cache (A=2, L=6) and
+ * the Cortex-A8 L2 cache (A=3, L=6).
+ * A = log2(# of ways), L = log2(bytes per cache line).
+ * see armv7 arch ref p. 1403.
+ */
+#define L1WAYSH 30
+#define L1SETSH 6
+#define L2WAYSH 29
+#define L2SETSH 6
+
+#define VARSTACK (8*4)		/* generous stack space for local variables */
+
+/* first argument (in R0) is the function to call in the innermost loop */
+/* second argument (in R8 when called from assembler) is cache level */
+TEXT cacheall+0(SB), 1, $-4
+//	MOVW	lvl+4(FP), R8	/* cache level */
+	MOVM.DB.W [R14,R1-R8], (R13) /* save regs on stack */
+	MOVW	R0, R1		/* save argument for inner loop in R1 */
+
+	MOVW	CPSR, R0
+	MOVM.DB.W [R0], (R13)	/* push CPSR */
+
+	ORR	$(PsrDirq|PsrDfiq), R0
+	MOVW	R0, CPSR	/* splhi */
+	BARRIERS
+
+	/* we may not have the MMU on yet, so map R1 to PC's space */
+	BIC	$KSEGM,	R1	/* strip segment from address */
+	MOVW	PC, R2		/* get PC's segment ... */
+	AND	$KSEGM, R2
+	CMP	$0, R2		/* PC segment should be non-zero on omap */
+	BEQ	buggery
+	ORR	R2, R1		/* combine them */
+
+	/* drain write buffers */
+	BARRIERS
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait
+	BARRIERS
+
+	SUB	$1, R8, R0	/* R0 = cache - 1 */
+	SLL	$1, R0		/* R0 = (cache - 1) << 1 */
+
+	/* set cache size select */
+	MCR	CpSC, CpIDcssel, R0, C(CpID), C(CpIDid), 0
+	BARRIERS
+
+	MRC	CpSC, CpIDcsize, R0, C(CpID), C(CpIDid), 0 /* get cache sizes */
+
+	/* compute # of ways and sets for this cache level */
+	SRA	$3, R0, R5	/* R5 (ways) = R0 >> 3 */
+	AND	$1023, R5	/* R5 = (R0 >> 3) & MASK(10) */
+	ADD	$1, R5		/* R5 = ((R0 >> 3) & MASK(10)) + 1 */
+	MOVW	R5, ways-20(SP)	/* ways = ((R0 >> 3) & MASK(10)) + 1 */
+
+	SRA	$13, R0, R3	/* R3 (sets) = R0 >> 13 */
+	AND	$32767, R3	/* R3 = (R0 >> 13) & MASK(15) */
+	ADD	$1, R3		/* R3 = ((R0 >> 13) & MASK(15)) + 1 */
+	MOVW	R3, sets-12(SP)	/* sets = ((R0 >> 13) & MASK(15)) + 1 */
+
+	/* force writes to stack out to dram */
+	BARRIERS
+	MOVW	R13, R0
+	SUB	$VARSTACK, R0	/* move down past local variables (ways) */
+	BIC	$(CACHELINESZ-1), R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEse
+	ADD	$CACHELINESZ, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEse
+	ADD	$CACHELINESZ, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEse
+	ADD	$CACHELINESZ, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEse
+	BARRIERS
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait
+	BARRIERS
+
+	/* iterate over ways */
+	MOVW	$0, R7		/* R7: way */
+	B	midtest
+middle:
+	ADD	$1, R7		/* way++ */
+midtest:
+	MOVW	ways-20(SP), R2	/* R2: ways */
+	CMP	R2, R7		/* way >= ways? */
+	BGE	ret		/* then done */
+
+	/* iterate over sets */
+	MOVW	$0, R6		/* R6: set */
+	B	intest
+inner:
+	ADD	$1, R6		/* set++ */
+intest:
+	MOVW	sets-12(SP), R5	/* R5: sets */
+	CMP	R5, R6		/* set >= sets? */
+	BGE	middle		/* then back to reinit of previous loop */
+
+	/* compute set/way register contents */
+	SUB	$1, R8, R4	/* R4 = cache - 1 */
+	CMP	$1, R8		/* cache == 1? */
+	SLL.EQ	$L1WAYSH, R7, R2 /* yes: R2 = way << L1WAYSH */
+	SLL.NE	$L2WAYSH, R7, R2 /* no:  R2 = way << L2WAYSH */
+	ORR	R4<<1, R2, R4	/* R4 = way << L?WAYSH | (cache - 1) << 1 */
+	ORR.EQ	R6<<L1SETSH, R4, R0 /* R0=way<<L1WAYSH|(cache-1)<<1|set<<L1SETSH */
+	ORR.NE	R6<<L2SETSH, R4, R0 /* R0=way<<L2WAYSH|(cache-1)<<1|set<<L2SETSH */
+
+	SUB	$VARSTACK, R13	/* move sp down past local variables (ways) */
+	/* must not use addresses relative to (SP) from here */
+	BL	(R1)		/* call asm to do something with R0 */
+	BARRIERS		/* make sure it has executed */
+	ADD	$VARSTACK, R13	/* restore sp */
+	/* may again use addresses relative to (SP) from here */
+
+	B	inner
+
+ret:
+	/* drain write buffers */
+	BARRIERS
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait
+	BARRIERS
+
+	MOVM.IA.W (R13), [R0]	/* pop CPSR */
+	MOVM.IA.W (R13), [R14,R1-R8] /* restore regs */
+	MOVW	R0, CPSR
+	BARRIERS
+	RET
+
+buggery:
+WAVE('?')
+	MOVW	PC, R0
+//	B	pczeroseg(SB)
+	RET

+ 490 - 0
sys/src/9/omap/clock.c

@@ -0,0 +1,490 @@
+/*
+ * omap3530 clocks
+ *
+ * timers count up to zero.
+ *
+ * the source clock signals for the timers are sometimes selectable.  for
+ * WDTIMER[23] and GPTIMER12, it's always the 32kHz clock.  for the
+ * others, it can be the 32kHz clock or the system clock.  we use only
+ * WDTIMER2 and GPTIMER[12], and configure GPTIMER[12] in archomap.c to
+ * use the 32kHZ clock.  WDTIMER1 is not accessible to us on GP
+ * (general-purpose) omaps.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "arm.h"
+
+enum {
+	Debug		= 0,
+
+	Tn0		= PHYSTIMER1,
+	Tn1		= PHYSTIMER2,
+
+	Tn0irq		= 37,			/* base IRQ for all timers */
+
+	Freebase	= 1,			/* base of free-running timer */
+
+	/*
+	 * clock is 32K (32,768) Hz, so one tick is 30.517µs,
+	 * so 327.68 ticks is 10ms, 32.768 tick is 1ms.
+	 */
+	Clockfreqbase	= 32 * 1024,		/* base rate in Hz */
+	Tcycles		= Clockfreqbase / HZ,	/* cycles per clock tick */
+
+	MinPeriod	= (Tcycles / 100 < 2? 2: Tcycles / 100),
+	MaxPeriod	= Tcycles,
+
+	Dogtimeout	= 4 * Clockfreqbase,	/* 4 seconds */
+};
+
+enum {
+	/* ticpcfg bits */
+	Noidle		= 1<<3,
+	Softreset	= 1<<1,
+
+	/* tistat bits */
+	Resetdone	= 1<<0,
+
+	/* tisr/tier bits */
+	Ovf_it		= 1<<1,		/* gp: overflow intr */
+	Mat_it		= 1<<0,		/* gp: match intr */
+	Wdovf_it	= 1<<0,		/* wdog: overflow intr */
+
+	/* tclr bits */
+	Ar		= 1<<1,		/* gp only: autoreload mode overflow */
+	St		= 1<<0,		/* gp only: start the timer */
+};
+
+/* omap35x timer registers */
+typedef struct Timerregs Timerregs;
+struct Timerregs {
+	/* common to all timers, gp and watchdog */
+	uchar	pad0[0x10];
+	ulong	ticpcfg;
+	ulong	tistat;		/* ro: low bit: reset done */
+	ulong	tisr;
+	ulong	tier;
+	ulong	twer;
+	ulong	tclr;
+	ulong	tcrr;		/* counter: cycles to zero */
+	ulong	tldr;
+	ulong	ttgr;		/* trigger */
+	ulong	twps;		/* ro: write posted pending */
+
+	/* gp timers only, unused by us */
+	ulong	tmar;		/* value to compare with counter */
+	ulong	tcar1;		/* ro */
+	ulong	tsicr;
+	ulong	tcar2;		/* ro */
+	union {
+		ulong	tpir;	/* gp: 1 ms tick generation: +ve */
+		ulong	wspr;	/* wdog: start/stop control */
+	};
+	ulong	tnir;		/* 1 ms tick generation: -ve */
+	ulong	tcvr;		/* 1 ms tick generation: next counter value */
+	ulong	tocr;		/* intr mask for n ticks */
+	ulong	towr;
+};
+
+static int ticks; /* for sanity checking; m->ticks doesn't always get called */
+static Lock clklck;
+
+static ulong	rdcycles(void), rdbaseticks(void);
+
+/* write a watchdog timer's start/stop register */
+static void
+wdogwrss(Timerregs *tn, ulong val)
+{
+	while (tn->twps & (1 << 4))	/* pending write to start/stop reg? */
+		;
+	tn->wspr = val;
+	coherence();
+	while (tn->twps & (1 << 4))	/* pending write to start/stop reg? */
+		;
+}
+
+static void
+resetwait(Timerregs *tn)
+{
+	long bound;
+
+	for (bound = 400*1000*1000; !(tn->tistat & Resetdone) && bound > 0;
+	    bound--)
+		;
+	if (bound <= 0)
+		iprint("clock reset didn't complete\n");
+}
+
+
+static void
+wdogoff(Timerregs *tn)
+{
+	resetwait(tn);
+
+	wdogwrss(tn, 0xaaaa);		/* magic off sequence */
+	wdogwrss(tn, 0x5555);
+
+	tn->tldr = 1;
+	coherence();
+	tn->tcrr = 1;			/* paranoia */
+	coherence();
+}
+
+static void wdogassure(void);
+
+static void
+wdogon(Timerregs *tn)
+{
+	static int beenhere;
+
+	resetwait(tn);
+	tn->tldr = -Dogtimeout;
+	tn->tcrr = -Dogtimeout;
+	coherence();
+	wdogwrss(tn, 0xbbbb);		/* magic on sequence */
+	wdogwrss(tn, 0x4444);		/* magic on sequence */
+
+	if (!beenhere) {
+		beenhere = 1;
+		/* touching the dog is not quick, so do it infrequently */
+		addclock0link(wdogassure, HZ);
+	}
+}
+
+static void
+wdogassure(void)		/* reset the watch dog's counter */
+{
+	Timerregs *tn;
+
+	tn = (Timerregs *)PHYSWDOG;
+	wdogoff(tn);
+
+	tn->tcrr = -Dogtimeout;
+	coherence();
+
+	wdogon(tn);
+}
+
+static void
+clockintr(Ureg* ureg, void *arg)
+{
+	Timerregs *tn;
+	static int nesting;
+
+	ticks++;
+	coherence();
+
+	if (nesting == 0) {	/* if the clock interrupted itself, bail out */
+		++nesting;
+		timerintr(ureg, 0);
+		--nesting;
+	}
+
+	tn = arg;
+	tn->tisr = Ovf_it;			/* dismiss the interrupt */
+	coherence();
+}
+
+static void
+clockreset(Timerregs *tn)
+{
+	if (probeaddr((uintptr)&tn->ticpcfg) < 0)
+		panic("no clock at %#p", tn);
+	tn->ticpcfg = Softreset | Noidle;
+	coherence();
+	resetwait(tn);
+	tn->tier = tn->tclr = 0;
+	coherence();
+}
+
+/* stop clock interrupts and disable the watchdog timer */
+void
+clockshutdown(void)
+{
+	clockreset((Timerregs *)PHYSWDT2);
+	wdogoff((Timerregs *)PHYSWDT2);
+	clockreset((Timerregs *)PHYSWDT3);
+	wdogoff((Timerregs *)PHYSWDT3);
+
+	clockreset((Timerregs *)Tn0);
+	clockreset((Timerregs *)Tn1);
+}
+
+enum {
+	Instrs		= 10*1000*1000,
+};
+
+static long
+issue1loop(void)
+{
+	register int i;
+	long st;
+
+	st = rdbaseticks();
+	i = Instrs;
+	do {
+		--i; --i; --i; --i; --i;
+		--i; --i; --i; --i;
+	} while(--i >= 0);
+	return rdbaseticks() - st;
+}
+
+static long
+issue2loop(void)
+{
+	register int i, j;
+	long st;
+
+	st = rdbaseticks();
+	i = Instrs / 2;
+	j = 0;
+	do {
+		--i; --j; --i; --j;
+		--i; --j; --i; --j;
+		--j;
+	} while(--i >= 0);
+	return rdbaseticks() - st;
+}
+
+/* estimate instructions/s. using 32kHz clock */
+static void
+guessmips(long (*loop)(void), char *lab)
+{
+	int s;
+	long tcks;
+
+	do {
+		s = splhi();
+		tcks = loop();
+		splx(s);
+		if (tcks < 0)
+			iprint("again...");
+	} while (tcks < 0);
+	/*
+	 * Instrs instructions took tcks ticks @ Clockfreqbase Hz.
+	 */
+	s = ((vlong)Clockfreqbase * Instrs) / tcks / 1000000;
+	if (Debug)
+		iprint("%ud mips (%s-issue)", s, lab);
+	USED(s);
+}
+
+void
+clockinit(void)
+{
+	int i, s;
+	Timerregs *tn;
+
+	clockshutdown();
+
+	/* turn cycle counter on */
+	cpwrsc(0, CpCLD, CpCLDena, CpCLDenacyc, 1<<31);
+
+	/* turn all counters on and clear the cycle counter */
+	cpwrsc(0, CpCLD, CpCLDena, CpCLDenapmnc, 1<<2 | 1);
+
+	/* let users read the cycle counter directly */
+	cpwrsc(0, CpCLD, CpCLDena, CpCLDenapmnc, 1);
+
+	ilock(&clklck);
+	m->fastclock = 1;
+	m->ticks = ticks = 0;
+
+	/*
+	 * T0 is a freerunning timer (cycle counter); it wraps,
+	 * automatically reloads, and does not dispatch interrupts.
+	 */
+	tn = (Timerregs *)Tn0;
+	tn->tcrr = Freebase;			/* count up to 0 */
+	tn->tldr = Freebase;
+	coherence();
+	tn->tclr = Ar | St;
+	iunlock(&clklck);
+
+	/*
+	 * T1 is the interrupting timer and does not participate
+	 * in measuring time.  It is initially set to HZ.
+	 */
+	tn = (Timerregs *)Tn1;
+	irqenable(Tn0irq+1, clockintr, tn, "clock");
+	ilock(&clklck);
+	tn->tcrr = -Tcycles;			/* approx.; count up to 0 */
+	tn->tldr = -Tcycles;
+	coherence();
+	tn->tclr = Ar | St;
+	coherence();
+	tn->tier = Ovf_it;
+	coherence();
+	iunlock(&clklck);
+
+	/*
+	 * verify sanity of timer1
+	 */
+	s = spllo();			/* risky */
+	for (i = 0; i < 5 && ticks == 0; i++) {
+		delay(10);
+		cachedwbinvse(&ticks, sizeof ticks);
+	}
+	splx(s);
+	if (ticks == 0) {
+		if (tn->tcrr == 0)
+			panic("clock not interrupting");
+		else if (tn->tcrr == tn->tldr)
+			panic("clock not ticking at all");
+#ifdef PARANOID
+		else
+			panic("clock running very slowly");
+#endif
+	}
+
+	guessmips(issue1loop, "single");
+	if (Debug)
+		iprint(", ");
+	guessmips(issue2loop, "dual");
+	if (Debug)
+		iprint("\n");
+
+	/*
+	 * m->delayloop should be the number of delay loop iterations
+	 * needed to consume 1 ms.  2 is min. instructions in the delay loop.
+	 */
+	m->delayloop = m->cpuhz / (1000 * 2);
+	m->delayloop = m->cpuhz / 1000;		/* overestimate for safety */
+//	iprint("m->delayloop = %lud\n", m->delayloop);
+
+	/*
+	 *  desynchronize the processor clocks so that they all don't
+	 *  try to resched at the same time.
+	 */
+	delay(m->machno*2);
+}
+
+void
+watchdoginit(void)
+{
+	wdogassure();
+}
+
+ulong
+µs(void)
+{
+	return fastticks2us(fastticks(nil));
+}
+
+void
+timerset(Tval next)
+{
+	long offset;
+	Timerregs *tn = (Timerregs *)Tn1;
+	static Lock setlck;
+
+	ilock(&setlck);
+	offset = next - fastticks(nil);
+	if(offset < MinPeriod)
+		offset = MinPeriod;
+	else if(offset > MaxPeriod)
+		offset = MaxPeriod;
+	tn->tcrr = -offset;
+	coherence();
+	iunlock(&setlck);
+}
+
+static ulong
+rdcycles(void)
+{
+	ulong v;
+
+	/* reads 32-bit cycle counter (counting up) */
+	v = cprdsc(0, CpCLD, CpCLDcyc, 0);
+	/* keep it positive; prevent m->fastclock ever going to 0 */
+	return v == 0? 1: v;
+}
+
+static ulong
+rdbaseticks(void)
+{
+	ulong v;
+
+	v = ((Timerregs *)Tn0)->tcrr;		/* tcrr should be counting up */
+	/* keep it positive; prevent m->fastclock ever going to 0 */
+	return v == 0? 1: v;
+}
+
+ulong
+perfticks(void)
+{
+	return rdcycles();
+}
+
+long
+lcycles(void)
+{
+	return perfticks();
+}
+
+/*
+ * until 5[cal] inline vlong ops, avoid them where possible,
+ * they are currently slow function calls.
+ */
+typedef union Counter Counter;
+union Counter {
+	uvlong	uvl;
+	struct {			/* little-endian */
+		ulong	low;
+		ulong	high;
+	};
+};
+
+enum {
+	Fastvlongops	= 0,
+};
+
+uvlong
+fastticks(uvlong *hz)
+{
+	Counter now, sclnow;
+
+	if(hz)
+		*hz = m->cpuhz;
+	ilock(&clklck);
+	if (m->ticks > HZ/10 && m->fastclock == 0)
+		panic("fastticks: zero m->fastclock; ticks %lud fastclock %#llux",
+			m->ticks, m->fastclock);
+
+	now.uvl = m->fastclock;
+	now.low = rdcycles();
+	if(now.uvl < m->fastclock)	/* low bits must have wrapped */
+		now.high++;
+	m->fastclock = now.uvl;
+	coherence();
+
+	sclnow.uvl = now.uvl;
+	iunlock(&clklck);
+	return sclnow.uvl;
+}
+
+void
+microdelay(int l)
+{
+	int i;
+
+	l = l * (vlong)m->delayloop / 1000;
+	if(l <= 0)
+		l = 1;
+	for(i = 0; i < l; i++)
+		;
+}
+
+void
+delay(int l)
+{
+	ulong i, j;
+
+	j = m->delayloop;
+	while(l-- > 0)
+		for(i=0; i < j; i++)
+			;
+}

+ 300 - 0
sys/src/9/omap/dat.h

@@ -0,0 +1,300 @@
+#define LASTRESORT lastresortprint	/* for ../port/devcons.c */
+#define CRUDEPRINT serialputs		/* for ../port/devcons.c */
+
+/*
+ * Time.
+ *
+ * HZ should divide 1000 evenly, ideally.
+ * 100, 125, 200, 250 and 333 are okay.
+ */
+#define	HZ		100			/* clock frequency */
+#define	MS2HZ		(1000/HZ)		/* millisec per clock tick */
+#define	TK2SEC(t)	((t)/HZ)		/* ticks to seconds */
+
+/*
+ * More accurate time
+ */
+#define MS2TMR(t)	((ulong)(((uvlong)(t) * m->cpuhz)/1000))
+#define US2TMR(t)	((ulong)(((uvlong)(t) * m->cpuhz)/1000000))
+
+/*
+ * we ignore the first 2 uarts on the omap3530 (see uarti8250.c) and use the
+ * third one but call it 0.
+ */
+#define CONSOLE 0
+
+typedef struct Conf	Conf;
+typedef struct Confmem	Confmem;
+typedef struct FPsave	FPsave;
+typedef struct ISAConf	ISAConf;
+typedef struct Label	Label;
+typedef struct Lock	Lock;
+typedef struct Memcache	Memcache;
+typedef struct MMMU	MMMU;
+typedef struct Mach	Mach;
+typedef u32int Mreg;				/* Msr - bloody UART */
+typedef struct Notsave	Notsave;
+typedef struct Page	Page;
+typedef struct PhysUart	PhysUart;
+typedef struct PMMU	PMMU;
+typedef struct Proc	Proc;
+typedef u32int		PTE;
+typedef struct Uart	Uart;
+typedef struct Ureg	Ureg;
+typedef uvlong		Tval;
+
+#pragma incomplete Ureg
+
+#define MAXSYSARG	5	/* for mount(fd, mpt, flag, arg, srv) */
+
+/*
+ *  parameters for sysproc.c
+ */
+#define AOUT_MAGIC	(E_MAGIC)
+
+struct Lock
+{
+	ulong	key;
+	u32int	sr;
+	uintptr	pc;
+	Proc*	p;
+	Mach*	m;
+	int	isilock;
+};
+
+struct Label
+{
+	uintptr	sp;
+	uintptr	pc;
+};
+
+/*
+ * emulated floating point
+ */
+struct FPsave
+{
+	ulong	status;
+	ulong	control;
+	ulong	regs[8][3];
+
+	int	fpstate;
+};
+
+/*
+ * FPsave.status
+ */
+enum
+{
+	FPinit,
+	FPactive,
+	FPinactive,
+};
+
+struct Confmem
+{
+	uintptr	base;
+	usize	npage;
+	uintptr	limit;
+	uintptr	kbase;
+	uintptr	klimit;
+};
+
+struct Conf
+{
+	ulong	nmach;		/* processors */
+	ulong	nproc;		/* processes */
+	Confmem	mem[1];		/* physical memory */
+	ulong	npage;		/* total physical pages of memory */
+	usize	upages;		/* user page pool */
+	ulong	copymode;	/* 0 is copy on write, 1 is copy on reference */
+	ulong	ialloc;		/* max interrupt time allocation in bytes */
+	ulong	pipeqsize;	/* size in bytes of pipe queues */
+	ulong	nimage;		/* number of page cache image headers */
+	ulong	nswap;		/* number of swap pages */
+	int	nswppo;		/* max # of pageouts per segment pass */
+	ulong	hz;		/* processor cycle freq */
+	ulong	mhz;
+};
+
+/*
+ *  things saved in the Proc structure during a notify
+ */
+struct Notsave {
+	int	emptiness;
+};
+
+/*
+ *  MMU stuff in Mach.
+ */
+struct MMMU
+{
+	PTE*	mmul1;		/* l1 for this processor */
+	int	mmul1lo;
+	int	mmul1hi;
+	int	mmupid;
+};
+
+/*
+ *  MMU stuff in proc
+ */
+#define NCOLOR	1		/* 1 level cache, don't worry about VCE's */
+struct PMMU
+{
+	Page*	mmul2;
+	Page*	mmul2cache;	/* free mmu pages */
+};
+
+#include "../port/portdat.h"
+
+struct Mach
+{
+	int	machno;			/* physical id of processor */
+	uintptr	splpc;			/* pc of last caller to splhi */
+
+	Proc*	proc;			/* current process */
+
+	MMMU;
+	int	flushmmu;		/* flush current proc mmu state */
+
+	ulong	ticks;			/* of the clock since boot time */
+	Label	sched;			/* scheduler wakeup */
+	Lock	alarmlock;		/* access to alarm list */
+	void*	alarm;			/* alarms bound to this clock */
+	int	inclockintr;
+
+	Proc*	readied;		/* for runproc */
+	ulong	schedticks;		/* next forced context switch */
+
+	int	cputype;
+	ulong	delayloop;
+
+	/* stats */
+	int	tlbfault;
+	int	tlbpurge;
+	int	pfault;
+	int	cs;
+	int	syscall;
+	int	load;
+	int	intr;
+	uvlong	fastclock;		/* last sampled value */
+	uvlong	inidle;			/* time spent in idlehands() */
+	ulong	spuriousintr;
+	int	lastintr;
+	int	ilockdepth;
+	Perf	perf;			/* performance counters */
+
+
+	int	cpumhz;
+	uvlong	cpuhz;			/* speed of cpu */
+	uvlong	cyclefreq;		/* Frequency of user readable cycle counter */
+
+	/* save areas for exceptions, hold R0-R4 */
+	u32int	sfiq[5];
+	u32int	sirq[5];
+	u32int	sund[5];
+	u32int	sabt[5];
+	u32int	smon[5];		/* probably not needed */
+	u32int	ssys[5];
+
+	int	stack[1];
+};
+
+/*
+ * Fake kmap.
+ */
+typedef void		KMap;
+#define	VA(k)		((uintptr)(k))
+#define	kmap(p)		(KMap*)((p)->pa|kseg0)
+#define	kunmap(k)
+
+struct
+{
+	Lock;
+	int	machs;			/* bitmap of active CPUs */
+	int	exiting;		/* shutdown */
+	int	ispanic;		/* shutdown in response to a panic */
+}active;
+
+extern register Mach* m;			/* R10 */
+extern register Proc* up;			/* R9 */
+extern uintptr kseg0;
+extern Mach* machaddr[MAXMACH];
+extern ulong memsize;
+
+/*
+ *  a parsed plan9.ini line
+ */
+#define NISAOPT		8
+
+struct ISAConf {
+	char	*type;
+	ulong	port;
+	int	irq;
+	ulong	dma;
+	ulong	mem;
+	ulong	size;
+	ulong	freq;
+
+	int	nopt;
+	char	*opt[NISAOPT];
+};
+
+#define	MACHP(n)	(machaddr[n])
+
+/*
+ * Horrid. But the alternative is 'defined'.
+ */
+#ifdef _DBGC_
+#define DBGFLG		(dbgflg[_DBGC_])
+#else
+#define DBGFLG		(0)
+#endif /* _DBGC_ */
+
+int vflag;
+extern char dbgflg[256];
+
+#define dbgprint	print		/* for now */
+
+/*
+ *  hardware info about a device
+ */
+typedef struct {
+	ulong	port;	
+	int	size;
+} Devport;
+
+struct DevConf
+{
+	ulong	intnum;			/* interrupt number */
+	char	*type;			/* card type, malloced */
+	int	nports;			/* Number of ports */
+	Devport	*ports;			/* The ports themselves */
+};
+
+enum {
+	Dcache,
+	Icache,
+	Unified,
+};
+
+/* characteristics of a given cache level */
+struct Memcache {
+	uint	level;		/* 1 is nearest processor, 2 further away */
+	uint	l1ip;		/* l1 I policy */
+
+	uint	nways;		/* associativity */
+	uint	nsets;
+	uint	linelen;	/* bytes per cache line */
+	uint	setsways;
+
+	uint	log2linelen;
+	uint	waysh;		/* shifts for set/way register */
+	uint	setsh;
+};
+
+enum Dmamode {
+	Const,
+	Postincr,
+	Index,
+	Index2,
+};

+ 220 - 0
sys/src/9/omap/devarch.c

@@ -0,0 +1,220 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+
+#include "../ip/ip.h"
+
+enum {
+	Qdir = 0,
+	Qbase,
+
+	Qmax = 16,
+};
+
+typedef long Rdwrfn(Chan*, void*, long, vlong);
+
+static Rdwrfn *readfn[Qmax];
+static Rdwrfn *writefn[Qmax];
+
+static Dirtab archdir[Qmax] = {
+	".",		{ Qdir, 0, QTDIR },	0,	0555,
+};
+
+Lock archwlock;	/* the lock is only for changing archdir */
+int narchdir = Qbase;
+
+/*
+ * Add a file to the #P listing.  Once added, you can't delete it.
+ * You can't add a file with the same name as one already there,
+ * and you get a pointer to the Dirtab entry so you can do things
+ * like change the Qid version.  Changing the Qid path is disallowed.
+ */
+Dirtab*
+addarchfile(char *name, int perm, Rdwrfn *rdfn, Rdwrfn *wrfn)
+{
+	int i;
+	Dirtab d;
+	Dirtab *dp;
+
+	memset(&d, 0, sizeof d);
+	strcpy(d.name, name);
+	d.perm = perm;
+
+	lock(&archwlock);
+	if(narchdir >= Qmax){
+		unlock(&archwlock);
+		return nil;
+	}
+
+	for(i=0; i<narchdir; i++)
+		if(strcmp(archdir[i].name, name) == 0){
+			unlock(&archwlock);
+			return nil;
+		}
+
+	d.qid.path = narchdir;
+	archdir[narchdir] = d;
+	readfn[narchdir] = rdfn;
+	writefn[narchdir] = wrfn;
+	dp = &archdir[narchdir++];
+	unlock(&archwlock);
+
+	return dp;
+}
+
+static Chan*
+archattach(char* spec)
+{
+	return devattach('P', spec);
+}
+
+Walkqid*
+archwalk(Chan* c, Chan *nc, char** name, int nname)
+{
+	return devwalk(c, nc, name, nname, archdir, narchdir, devgen);
+}
+
+static int
+archstat(Chan* c, uchar* dp, int n)
+{
+	return devstat(c, dp, n, archdir, narchdir, devgen);
+}
+
+static Chan*
+archopen(Chan* c, int omode)
+{
+	return devopen(c, omode, archdir, narchdir, devgen);
+}
+
+static void
+archclose(Chan*)
+{
+}
+
+static long
+archread(Chan *c, void *a, long n, vlong offset)
+{
+	Rdwrfn *fn;
+
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, a, n, archdir, narchdir, devgen);
+
+	default:
+		if(c->qid.path < narchdir && (fn = readfn[c->qid.path]))
+			return fn(c, a, n, offset);
+		error(Eperm);
+		break;
+	}
+
+	return 0;
+}
+
+static long
+archwrite(Chan *c, void *a, long n, vlong offset)
+{
+	Rdwrfn *fn;
+
+	if(c->qid.path < narchdir && (fn = writefn[c->qid.path]))
+		return fn(c, a, n, offset);
+	error(Eperm);
+
+	return 0;
+}
+
+void archinit(void);
+
+Dev archdevtab = {
+	'P',
+	"arch",
+
+	devreset,
+	archinit,
+	devshutdown,
+	archattach,
+	archwalk,
+	archstat,
+	archopen,
+	devcreate,
+	archclose,
+	archread,
+	devbread,
+	archwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+/* convert AddrDevid register to a string in buf and return buf */
+char *
+cputype2name(char *buf, int size)
+{
+#ifdef TODO		/* identify the flavour of omap3530 SoC & cpu */
+	char *soc;
+
+	m->cputype = *(ulong *)AddrDevid;
+	switch(m->cputype & 3) {
+	case 0:
+//		soc = "88F6180";
+		break;
+	case 1:
+//		soc = "88F619[02]";
+		break;
+	case 2:
+//		soc = "88F6281";
+		break;
+	default:
+		soc = "unknown";
+		break;
+	}
+#endif
+	seprint(buf, buf + size, "Cortex-A8");
+	return buf;
+}
+
+static long
+cputyperead(Chan*, void *a, long n, vlong offset)
+{
+	char name[64], str[128];
+
+	cputype2name(name, sizeof name);
+	snprint(str, sizeof str, "ARM %s %llud\n", name,
+		m->cpuhz / (1000*1000));
+	return readstr(offset, a, n, str);
+}
+
+static long
+tbread(Chan*, void *a, long n, vlong offset)
+{
+	char str[16];
+	uvlong tb;
+
+	cycles(&tb);
+
+	snprint(str, sizeof(str), "%16.16llux", tb);
+	return readstr(offset, a, n, str);
+}
+
+static long
+nsread(Chan*, void *a, long n, vlong offset)
+{
+	char str[16];
+	uvlong tb;
+
+	cycles(&tb);
+
+	snprint(str, sizeof(str), "%16.16llux", (tb/700)* 1000);
+	return readstr(offset, a, n, str);
+}
+
+void
+archinit(void)
+{
+	addarchfile("cputype", 0444, cputyperead, nil);
+	addarchfile("timebase",0444, tbread, nil);
+//	addarchfile("nsec", 0444, nsread, nil);
+}

+ 1368 - 0
sys/src/9/omap/devcons.c

@@ -0,0 +1,1368 @@
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"../port/error.h"
+#include	"pool.h"
+
+#include	<authsrv.h>
+
+void	(*consdebug)(void) = nil;
+void	(*screenputs)(char*, int) = nil;
+
+Queue*	kbdq;			/* unprocessed console input */
+Queue*	lineq;			/* processed console input */
+Queue*	serialoq;		/* serial console output */
+Queue*	kprintoq;		/* console output, for /dev/kprint */
+ulong	kprintinuse;		/* test and set whether /dev/kprint is open */
+int	iprintscreenputs = 1;
+
+int	panicking;
+
+static struct
+{
+	QLock;
+
+	int	raw;		/* true if we shouldn't process input */
+	Ref	ctl;		/* number of opens to the control file */
+	int	x;		/* index into line */
+	char	line[1024];	/* current input line */
+
+	int	count;
+	int	ctlpoff;
+
+	/* a place to save up characters at interrupt time before dumping them in the queue */
+	Lock	lockputc;
+	char	istage[1024];
+	char	*iw;
+	char	*ir;
+	char	*ie;
+} kbd = {
+	.iw	= kbd.istage,
+	.ir	= kbd.istage,
+	.ie	= kbd.istage + sizeof(kbd.istage),
+};
+
+char	*sysname;
+vlong	fasthz;
+
+static void	seedrand(void);
+static int	readtime(ulong, char*, int);
+static int	readbintime(char*, int);
+static int	writetime(char*, int);
+static int	writebintime(char*, int);
+
+enum
+{
+	CMhalt,
+	CMreboot,
+	CMpanic,
+};
+
+Cmdtab rebootmsg[] =
+{
+	CMhalt,		"halt",		1,
+	CMreboot,	"reboot",	0,
+	CMpanic,	"panic",	0,
+};
+
+void
+printinit(void)
+{
+	lineq = qopen(2*1024, 0, nil, nil);
+	if(lineq == nil)
+		panic("printinit");
+	qnoblock(lineq, 1);
+}
+
+int
+consactive(void)
+{
+	if(serialoq)
+		return qlen(serialoq) > 0;
+	return 0;
+}
+
+void
+prflush(void)
+{
+	ulong now;
+
+	now = m->ticks;
+	while(consactive())
+		if(m->ticks - now >= HZ)
+			break;
+}
+
+/*
+ * Log console output so it can be retrieved via /dev/kmesg.
+ * This is good for catching boot-time messages after the fact.
+ */
+struct {
+	Lock lk;
+//	char buf[16384];		/* normal */
+	char buf[256*1024];		/* for acpi debugging */
+	uint n;
+} kmesg;
+
+static void
+kmesgputs(char *str, int n)
+{
+	uint nn, d;
+
+	ilock(&kmesg.lk);
+	/* take the tail of huge writes */
+	if(n > sizeof kmesg.buf){
+		d = n - sizeof kmesg.buf;
+		str += d;
+		n -= d;
+	}
+
+	/* slide the buffer down to make room */
+	nn = kmesg.n;
+	if(nn + n >= sizeof kmesg.buf){
+		d = nn + n - sizeof kmesg.buf;
+		if(d)
+			memmove(kmesg.buf, kmesg.buf+d, sizeof kmesg.buf-d);
+		nn -= d;
+	}
+
+	/* copy the data in */
+	memmove(kmesg.buf+nn, str, n);
+	nn += n;
+	kmesg.n = nn;
+	iunlock(&kmesg.lk);
+}
+
+/*
+ *   Print a string on the console.  Convert \n to \r\n for serial
+ *   line consoles.  Locking of the queues is left up to the screen
+ *   or uart code.  Multi-line messages to serial consoles may get
+ *   interspersed with other messages.
+ */
+static void
+putstrn0(char *str, int n, int usewrite)
+{
+#ifdef CRUDEPRINT
+	CRUDEPRINT(str, n);
+	USED(usewrite);
+#else
+	int m;
+	char *t;
+
+	if(!islo())
+		usewrite = 0;
+
+	/*
+	 *  how many different output devices do we need?
+	 */
+	kmesgputs(str, n);
+
+	/*
+	 *  if someone is reading /dev/kprint,
+	 *  put the message there.
+	 *  if not and there's an attached bit mapped display,
+	 *  put the message there.
+	 *
+	 *  if there's a serial line being used as a console,
+	 *  put the message there.
+	 */
+	if(kprintoq != nil && !qisclosed(kprintoq)){
+		if(usewrite)
+			qwrite(kprintoq, str, n);
+		else
+			qiwrite(kprintoq, str, n);
+	}else if(screenputs != nil)
+		screenputs(str, n);
+
+	if(serialoq == nil){
+		uartputs(str, n);
+		return;
+	}
+
+	while(n > 0) {
+		t = memchr(str, '\n', n);
+		if(t && !kbd.raw) {
+			m = t-str;
+			if(usewrite){
+				qwrite(serialoq, str, m);
+				qwrite(serialoq, "\r\n", 2);
+			} else {
+				qiwrite(serialoq, str, m);
+				qiwrite(serialoq, "\r\n", 2);
+			}
+			n -= m+1;
+			str = t+1;
+		} else {
+			if(usewrite)
+				qwrite(serialoq, str, n);
+			else
+				qiwrite(serialoq, str, n);
+			break;
+		}
+	}
+#endif
+}
+
+void
+putstrn(char *str, int n)
+{
+	putstrn0(str, n, 0);
+}
+
+int noprint;
+
+int
+print(char *fmt, ...)
+{
+	int n;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	if(noprint)
+		return -1;
+
+	va_start(arg, fmt);
+	n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	putstrn(buf, n);
+
+	return n;
+}
+
+/*
+ * Want to interlock iprints to avoid interlaced output on 
+ * multiprocessor, but don't want to deadlock if one processor
+ * dies during print and another has something important to say.
+ * Make a good faith effort.
+ */
+static Lock iprintlock;
+static int
+iprintcanlock(Lock *l)
+{
+	int i;
+	
+	for(i=0; i<1000; i++){
+		if(canlock(l))
+			return 1;
+		if(l->m == MACHP(m->machno))
+			return 0;
+		microdelay(100);
+	}
+	return 0;
+}
+
+int
+iprint(char *fmt, ...)
+{
+	int n, s, locked;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	s = splhi();
+	va_start(arg, fmt);
+	n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	locked = iprintcanlock(&iprintlock);
+	if(screenputs != nil && iprintscreenputs)
+		screenputs(buf, n);
+#ifdef CRUDEPRINT
+	CRUDEPRINT(buf, n);
+#else
+	uartputs(buf, n);
+#endif
+	if(locked)
+		unlock(&iprintlock);
+	splx(s);
+
+	return n;
+}
+
+void
+panic(char *fmt, ...)
+{
+	int n, s;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	kprintoq = nil;	/* don't try to write to /dev/kprint */
+
+	if(panicking)
+		for(;;);
+	panicking = 1;
+
+	delay(20);
+	s = splhi();
+	strcpy(buf, "panic: ");
+	va_start(arg, fmt);
+	n = vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	iprint("%s\n", buf);
+	if(consdebug)
+		(*consdebug)();
+	splx(s);
+	prflush();
+	buf[n] = '\n';
+	putstrn(buf, n+1);
+	dumpstack();
+
+	exit(1);
+}
+
+/* libmp at least contains a few calls to sysfatal; simulate with panic */
+void
+sysfatal(char *fmt, ...)
+{
+	char err[256];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(err, err + sizeof err, fmt, arg);
+	va_end(arg);
+	panic("sysfatal: %s", err);
+}
+
+void
+_assert(char *fmt)
+{
+	panic("assert failed at %#p: %s", getcallerpc(&fmt), fmt);
+}
+
+int
+pprint(char *fmt, ...)
+{
+	int n;
+	Chan *c;
+	va_list arg;
+	char buf[2*PRINTSIZE];
+
+	if(up == nil || up->fgrp == nil)
+		return 0;
+
+	c = up->fgrp->fd[2];
+	if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR))
+		return 0;
+	n = snprint(buf, sizeof buf, "%s %lud: ", up->text, up->pid);
+	va_start(arg, fmt);
+	n = vseprint(buf+n, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+
+	if(waserror())
+		return 0;
+	devtab[c->type]->write(c, buf, n, c->offset);
+	poperror();
+
+	lock(c);
+	c->offset += n;
+	unlock(c);
+
+	return n;
+}
+
+static void
+echoscreen(char *buf, int n)
+{
+	char *e, *p;
+	char ebuf[128];
+	int x;
+
+iprint("echoscreen: wrong for beagle\n");
+	p = ebuf;
+	e = ebuf + sizeof(ebuf) - 4;
+	while(n-- > 0){
+		if(p >= e){
+			screenputs(ebuf, p - ebuf);
+			p = ebuf;
+		}
+		x = *buf++;
+		if(x == 0x15){
+			*p++ = '^';
+			*p++ = 'U';
+			*p++ = '\n';
+		} else
+			*p++ = x;
+	}
+	if(p != ebuf)
+		screenputs(ebuf, p - ebuf);
+}
+
+static void
+echoserialoq(char *buf, int n)
+{
+#ifdef ECHO_WORKS
+	int x;
+	char *e, *p;
+	char ebuf[128];
+
+	p = ebuf;
+	e = ebuf + sizeof(ebuf) - 4;
+	while(n-- > 0){
+		if(p >= e){
+			qiwrite(serialoq, ebuf, p - ebuf);
+			p = ebuf;
+		}
+		x = *buf++;
+		if(x == '\n'){
+			*p++ = '\r';
+			*p++ = '\n';
+		} else if(x == 0x15){
+			*p++ = '^';
+			*p++ = 'U';
+			*p++ = '\n';
+		} else
+			*p++ = x;
+	}
+	if(p != ebuf)
+		qiwrite(serialoq, ebuf, p - ebuf);
+#else
+	/*
+	 * TODO: something is broken about the above and echoed characters
+	 * don't appear.  until we figure it out, just brute force it.
+	 */
+	while(n-- > 0){
+		if(*buf == ('u' & 037))
+			CRUDEPRINT("^U\r\n", 4);
+		else
+			CRUDEPRINT(buf, 1);
+		buf++;
+	}
+#endif
+}
+
+static void
+echo(char *buf, int n)
+{
+	static int ctrlt, pid;
+	int x;
+	char *e, *p;
+
+	if(n == 0)
+		return;
+
+	e = buf+n;
+	for(p = buf; p < e; p++){
+		switch(*p){
+		case 0x10:	/* ^P */
+			if(cpuserver && !kbd.ctlpoff){
+				active.exiting = 1;
+				return;
+			}
+			break;
+		case 0x14:	/* ^T */
+			ctrlt++;
+			if(ctrlt > 2)
+				ctrlt = 2;
+			continue;
+		}
+
+		if(ctrlt != 2)
+			continue;
+
+		/* ^T escapes */
+		ctrlt = 0;
+		switch(*p){
+		case 'S':
+			x = splhi();
+			dumpstack();
+			procdump();
+			splx(x);
+			return;
+		case 's':
+			dumpstack();
+			return;
+		case 'x':
+			xsummary();
+			ixsummary();
+			mallocsummary();
+		//	memorysummary();
+			pagersummary();
+			return;
+		case 'd':
+			if(consdebug == nil)
+				consdebug = rdb;
+			else
+				consdebug = nil;
+			print("consdebug now %#p\n", consdebug);
+			return;
+		case 'D':
+			if(consdebug == nil)
+				consdebug = rdb;
+			consdebug();
+			return;
+		case 'p':
+			x = spllo();
+			procdump();
+			splx(x);
+			return;
+		case 'q':
+			scheddump();
+			return;
+		case 'k':
+			killbig("^t ^t k");
+			return;
+		case 'r':
+			exit(0);
+			return;
+		}
+	}
+
+	qproduce(kbdq, buf, n);
+	if(kbd.raw)
+		return;
+	kmesgputs(buf, n);
+	if(screenputs != nil)
+		echoscreen(buf, n);
+	if(serialoq)
+		echoserialoq(buf, n);
+}
+
+/*
+ *  Called by a uart interrupt for console input.
+ *
+ *  turn '\r' into '\n' before putting it into the queue.
+ */
+int
+kbdcr2nl(Queue*, int ch)
+{
+	char *next;
+
+	ilock(&kbd.lockputc);		/* just a mutex */
+	if(ch == '\r' && !kbd.raw)
+		ch = '\n';
+	next = kbd.iw+1;
+	if(next >= kbd.ie)
+		next = kbd.istage;
+	if(next != kbd.ir){
+		*kbd.iw = ch;
+		kbd.iw = next;
+	}
+	iunlock(&kbd.lockputc);
+	return 0;
+}
+
+/*
+ *  Put character, possibly a rune, into read queue at interrupt time.
+ *  Called at interrupt time to process a character.
+ */
+int
+kbdputc(Queue*, int ch)
+{
+	int i, n;
+	char buf[3];
+	Rune r;
+	char *next;
+
+	if(kbd.ir == nil)
+		return 0;		/* in case we're not inited yet */
+	
+	ilock(&kbd.lockputc);		/* just a mutex */
+	r = ch;
+	n = runetochar(buf, &r);
+	for(i = 0; i < n; i++){
+		next = kbd.iw+1;
+		if(next >= kbd.ie)
+			next = kbd.istage;
+		if(next == kbd.ir)
+			break;
+		*kbd.iw = buf[i];
+		kbd.iw = next;
+	}
+	iunlock(&kbd.lockputc);
+	return 0;
+}
+
+/*
+ *  we save up input characters till clock time to reduce
+ *  per character interrupt overhead.
+ */
+static void
+kbdputcclock(void)
+{
+	char *iw;
+
+	/* this amortizes cost of qproduce */
+	if(kbd.iw != kbd.ir){
+		iw = kbd.iw;
+		if(iw < kbd.ir){
+			echo(kbd.ir, kbd.ie-kbd.ir);
+			kbd.ir = kbd.istage;
+		}
+		if(kbd.ir != iw){
+			echo(kbd.ir, iw-kbd.ir);
+			kbd.ir = iw;
+		}
+	}
+}
+
+enum{
+	Qdir,
+	Qbintime,
+	Qcons,
+	Qconsctl,
+	Qcputime,
+	Qdrivers,
+	Qkmesg,
+	Qkprint,
+	Qhostdomain,
+	Qhostowner,
+	Qnull,
+	Qosversion,
+	Qpgrpid,
+	Qpid,
+	Qppid,
+	Qrandom,
+	Qreboot,
+	Qswap,
+	Qsysname,
+	Qsysstat,
+	Qtime,
+	Quser,
+	Qzero,
+};
+
+enum
+{
+	VLNUMSIZE=	22,
+};
+
+static Dirtab consdir[]={
+	".",	{Qdir, 0, QTDIR},	0,		DMDIR|0555,
+	"bintime",	{Qbintime},	24,		0664,
+	"cons",		{Qcons},	0,		0660,
+	"consctl",	{Qconsctl},	0,		0220,
+	"cputime",	{Qcputime},	6*NUMSIZE,	0444,
+	"drivers",	{Qdrivers},	0,		0444,
+	"hostdomain",	{Qhostdomain},	DOMLEN,		0664,
+	"hostowner",	{Qhostowner},	0,		0664,
+	"kmesg",	{Qkmesg},	0,		0440,
+	"kprint",	{Qkprint, 0, QTEXCL},	0,	DMEXCL|0440,
+	"null",		{Qnull},	0,		0666,
+	"osversion",	{Qosversion},	0,		0444,
+	"pgrpid",	{Qpgrpid},	NUMSIZE,	0444,
+	"pid",		{Qpid},		NUMSIZE,	0444,
+	"ppid",		{Qppid},	NUMSIZE,	0444,
+	"random",	{Qrandom},	0,		0444,
+	"reboot",	{Qreboot},	0,		0664,
+	"swap",		{Qswap},	0,		0664,
+	"sysname",	{Qsysname},	0,		0664,
+	"sysstat",	{Qsysstat},	0,		0666,
+	"time",		{Qtime},	NUMSIZE+3*VLNUMSIZE,	0664,
+	"user",		{Quser},	0,		0666,
+	"zero",		{Qzero},	0,		0444,
+};
+
+int
+readnum(ulong off, char *buf, ulong n, ulong val, int size)
+{
+	char tmp[64];
+
+	snprint(tmp, sizeof(tmp), "%*lud", size-1, val);
+	tmp[size-1] = ' ';
+	if(off >= size)
+		return 0;
+	if(off+n > size)
+		n = size-off;
+	memmove(buf, tmp+off, n);
+	return n;
+}
+
+int
+readstr(ulong off, char *buf, ulong n, char *str)
+{
+	int size;
+
+	size = strlen(str);
+	if(off >= size)
+		return 0;
+	if(off+n > size)
+		n = size-off;
+	memmove(buf, str+off, n);
+	return n;
+}
+
+static void
+consinit(void)
+{
+	todinit();
+	randominit();
+	/*
+	 * at 115200 baud, the 1024 char buffer takes 56 ms to process,
+	 * processing it every 22 ms should be fine
+	 */
+	addclock0link(kbdputcclock, 22);
+}
+
+static Chan*
+consattach(char *spec)
+{
+	return devattach('c', spec);
+}
+
+static Walkqid*
+conswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
+}
+
+static int
+consstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, consdir, nelem(consdir), devgen);
+}
+
+static Chan*
+consopen(Chan *c, int omode)
+{
+	c->aux = nil;
+	c = devopen(c, omode, consdir, nelem(consdir), devgen);
+	switch((ulong)c->qid.path){
+	case Qconsctl:
+		incref(&kbd.ctl);
+		break;
+
+	case Qkprint:
+		if(tas(&kprintinuse) != 0){
+			c->flag &= ~COPEN;
+			error(Einuse);
+		}
+		if(kprintoq == nil){
+			kprintoq = qopen(8*1024, Qcoalesce, 0, 0);
+			if(kprintoq == nil){
+				c->flag &= ~COPEN;
+				error(Enomem);
+			}
+			qnoblock(kprintoq, 1);
+		}else
+			qreopen(kprintoq);
+		c->iounit = qiomaxatomic;
+		break;
+	}
+	return c;
+}
+
+static void
+consclose(Chan *c)
+{
+	switch((ulong)c->qid.path){
+	/* last close of control file turns off raw */
+	case Qconsctl:
+		if(c->flag&COPEN){
+			if(decref(&kbd.ctl) == 0)
+				kbd.raw = 0;
+		}
+		break;
+
+	/* close of kprint allows other opens */
+	case Qkprint:
+		if(c->flag & COPEN){
+			kprintinuse = 0;
+			qhangup(kprintoq, nil);
+		}
+		break;
+	}
+}
+
+static long
+consread(Chan *c, void *buf, long n, vlong off)
+{
+	ulong l;
+	Mach *mp;
+	char *b, *bp, ch;
+	char tmp[256];		/* must be >= 18*NUMSIZE (Qswap) */
+	int i, k, id, send;
+	vlong offset = off;
+
+	if(n <= 0)
+		return n;
+
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, buf, n, consdir, nelem(consdir), devgen);
+
+	case Qcons:
+		qlock(&kbd);
+		if(waserror()) {
+			qunlock(&kbd);
+			nexterror();
+		}
+		while(!qcanread(lineq)){
+			if(qread(kbdq, &ch, 1) == 0)
+				continue;
+			send = 0;
+			if(ch == 0){
+				/* flush output on rawoff -> rawon */
+				if(kbd.x > 0)
+					send = !qcanread(kbdq);
+			}else if(kbd.raw){
+				kbd.line[kbd.x++] = ch;
+				send = !qcanread(kbdq);
+			}else{
+				switch(ch){
+				case '\b':
+					if(kbd.x > 0)
+						kbd.x--;
+					break;
+				case 0x15:	/* ^U */
+					kbd.x = 0;
+					break;
+				case '\n':
+				case 0x04:	/* ^D */
+					send = 1;
+				default:
+					if(ch != 0x04)
+						kbd.line[kbd.x++] = ch;
+					break;
+				}
+			}
+			if(send || kbd.x == sizeof kbd.line){
+				qwrite(lineq, kbd.line, kbd.x);
+				kbd.x = 0;
+			}
+		}
+		n = qread(lineq, buf, n);
+		qunlock(&kbd);
+		poperror();
+		return n;
+
+	case Qcputime:
+		k = offset;
+		if(k >= 6*NUMSIZE)
+			return 0;
+		if(k+n > 6*NUMSIZE)
+			n = 6*NUMSIZE - k;
+		/* easiest to format in a separate buffer and copy out */
+		for(i=0; i<6 && NUMSIZE*i<k+n; i++){
+			l = up->time[i];
+			if(i == TReal)
+				l = MACHP(0)->ticks - l;
+			l = TK2MS(l);
+			readnum(0, tmp+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
+		}
+		memmove(buf, tmp+k, n);
+		return n;
+
+	case Qkmesg:
+		/*
+		 * This is unlocked to avoid tying up a process
+		 * that's writing to the buffer.  kmesg.n never 
+		 * gets smaller, so worst case the reader will
+		 * see a slurred buffer.
+		 */
+		if(off >= kmesg.n)
+			n = 0;
+		else{
+			if(off+n > kmesg.n)
+				n = kmesg.n - off;
+			memmove(buf, kmesg.buf+off, n);
+		}
+		return n;
+		
+	case Qkprint:
+		return qread(kprintoq, buf, n);
+
+	case Qpgrpid:
+		return readnum((ulong)offset, buf, n, up->pgrp->pgrpid, NUMSIZE);
+
+	case Qpid:
+		return readnum((ulong)offset, buf, n, up->pid, NUMSIZE);
+
+	case Qppid:
+		return readnum((ulong)offset, buf, n, up->parentpid, NUMSIZE);
+
+	case Qtime:
+		return readtime((ulong)offset, buf, n);
+
+	case Qbintime:
+		return readbintime(buf, n);
+
+	case Qhostowner:
+		return readstr((ulong)offset, buf, n, eve);
+
+	case Qhostdomain:
+		return readstr((ulong)offset, buf, n, hostdomain);
+
+	case Quser:
+		return readstr((ulong)offset, buf, n, up->user);
+
+	case Qnull:
+		return 0;
+
+	case Qsysstat:
+		b = smalloc(conf.nmach*(NUMSIZE*11+1) + 1);	/* +1 for NUL */
+		bp = b;
+		for(id = 0; id < 32; id++) {
+			if(active.machs & (1<<id)) {
+				mp = MACHP(id);
+				readnum(0, bp, NUMSIZE, id, NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE, mp->cs, NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE, mp->intr, NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE, mp->syscall, NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE, mp->pfault, NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE, mp->tlbfault, NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE, mp->tlbpurge, NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE, mp->load, NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE,
+					(mp->perf.avg_inidle*100)/mp->perf.period,
+					NUMSIZE);
+				bp += NUMSIZE;
+				readnum(0, bp, NUMSIZE,
+					(mp->perf.avg_inintr*100)/mp->perf.period,
+					NUMSIZE);
+				bp += NUMSIZE;
+				*bp++ = '\n';
+			}
+		}
+		if(waserror()){
+			free(b);
+			nexterror();
+		}
+		n = readstr((ulong)offset, buf, n, b);
+		free(b);
+		poperror();
+		return n;
+
+	case Qswap:
+		snprint(tmp, sizeof tmp,
+			"%lud memory\n"
+			"%d pagesize\n"
+			"%lud kernel\n"
+			"%lud/%lud user\n"
+			"%lud/%lud swap\n"
+			"%lud/%lud kernel malloc\n"
+			"%lud/%lud kernel draw\n",
+			conf.npage*BY2PG,
+			BY2PG,
+			conf.npage-conf.upages,
+			palloc.user-palloc.freecount, palloc.user,
+			conf.nswap-swapalloc.free, conf.nswap,
+			mainmem->cursize, mainmem->maxsize,
+			imagmem->cursize, imagmem->maxsize);
+
+		return readstr((ulong)offset, buf, n, tmp);
+
+	case Qsysname:
+		if(sysname == nil)
+			return 0;
+		return readstr((ulong)offset, buf, n, sysname);
+
+	case Qrandom:
+		return randomread(buf, n);
+
+	case Qdrivers:
+		b = malloc(READSTR);
+		if(b == nil)
+			error(Enomem);
+		n = 0;
+		for(i = 0; devtab[i] != nil; i++)
+			n += snprint(b+n, READSTR-n, "#%C %s\n", devtab[i]->dc,  devtab[i]->name);
+		if(waserror()){
+			free(b);
+			nexterror();
+		}
+		n = readstr((ulong)offset, buf, n, b);
+		free(b);
+		poperror();
+		return n;
+
+	case Qzero:
+		memset(buf, 0, n);
+		return n;
+
+	case Qosversion:
+		snprint(tmp, sizeof tmp, "2000");
+		n = readstr((ulong)offset, buf, n, tmp);
+		return n;
+
+	default:
+		print("consread %#llux\n", c->qid.path);
+		error(Egreg);
+	}
+	return -1;		/* never reached */
+}
+
+static long
+conswrite(Chan *c, void *va, long n, vlong off)
+{
+	char buf[256], ch;
+	long l, bp;
+	char *a;
+	Mach *mp;
+	int id, fd;
+	Chan *swc;
+	ulong offset;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	a = va;
+	offset = off;
+
+	switch((ulong)c->qid.path){
+	case Qcons:
+		/*
+		 * Can't page fault in putstrn, so copy the data locally.
+		 */
+		l = n;
+		while(l > 0){
+			bp = l;
+			if(bp > sizeof buf)
+				bp = sizeof buf;
+			memmove(buf, a, bp);
+			putstrn0(buf, bp, 1);
+			a += bp;
+			l -= bp;
+		}
+		break;
+
+	case Qconsctl:
+		if(n >= sizeof(buf))
+			n = sizeof(buf)-1;
+		strncpy(buf, a, n);
+		buf[n] = 0;
+		for(a = buf; a;){
+			if(strncmp(a, "rawon", 5) == 0){
+				kbd.raw = 1;
+				/* clumsy hack - wake up reader */
+				ch = 0;
+				qwrite(kbdq, &ch, 1);			
+			} else if(strncmp(a, "rawoff", 6) == 0){
+				kbd.raw = 0;
+			} else if(strncmp(a, "ctlpon", 6) == 0){
+				kbd.ctlpoff = 0;
+			} else if(strncmp(a, "ctlpoff", 7) == 0){
+				kbd.ctlpoff = 1;
+			}
+			if(a = strchr(a, ' '))
+				a++;
+		}
+		break;
+
+	case Qtime:
+		if(!iseve())
+			error(Eperm);
+		return writetime(a, n);
+
+	case Qbintime:
+		if(!iseve())
+			error(Eperm);
+		return writebintime(a, n);
+
+	case Qhostowner:
+		return hostownerwrite(a, n);
+
+	case Qhostdomain:
+		return hostdomainwrite(a, n);
+
+	case Quser:
+		return userwrite(a, n);
+
+	case Qnull:
+		break;
+
+	case Qreboot:
+		if(!iseve())
+			error(Eperm);
+		cb = parsecmd(a, n);
+
+		if(waserror()) {
+			free(cb);
+			nexterror();
+		}
+		ct = lookupcmd(cb, rebootmsg, nelem(rebootmsg));
+		switch(ct->index) {
+		case CMhalt:
+			reboot(nil, 0, 0);
+			break;
+		case CMreboot:
+			rebootcmd(cb->nf-1, cb->f+1);
+			break;
+		case CMpanic:
+			*(ulong*)0=0;
+			panic("/dev/reboot");
+		}
+		poperror();
+		free(cb);
+		break;
+
+	case Qsysstat:
+		for(id = 0; id < 32; id++) {
+			if(active.machs & (1<<id)) {
+				mp = MACHP(id);
+				mp->cs = 0;
+				mp->intr = 0;
+				mp->syscall = 0;
+				mp->pfault = 0;
+				mp->tlbfault = 0;
+				mp->tlbpurge = 0;
+			}
+		}
+		break;
+
+	case Qswap:
+		if(n >= sizeof buf)
+			error(Egreg);
+		memmove(buf, va, n);	/* so we can NUL-terminate */
+		buf[n] = 0;
+		/* start a pager if not already started */
+		if(strncmp(buf, "start", 5) == 0){
+			kickpager();
+			break;
+		}
+		if(!iseve())
+			error(Eperm);
+		if(buf[0]<'0' || '9'<buf[0])
+			error(Ebadarg);
+		fd = strtoul(buf, 0, 0);
+		swc = fdtochan(fd, -1, 1, 1);
+		setswapchan(swc);
+		break;
+
+	case Qsysname:
+		if(offset != 0)
+			error(Ebadarg);
+		if(n <= 0 || n >= sizeof buf)
+			error(Ebadarg);
+		strncpy(buf, a, n);
+		buf[n] = 0;
+		if(buf[n-1] == '\n')
+			buf[n-1] = 0;
+		kstrdup(&sysname, buf);
+		break;
+
+	default:
+		print("conswrite: %#llux\n", c->qid.path);
+		error(Egreg);
+	}
+	return n;
+}
+
+Dev consdevtab = {
+	'c',
+	"cons",
+
+	devreset,
+	consinit,
+	devshutdown,
+	consattach,
+	conswalk,
+	consstat,
+	consopen,
+	devcreate,
+	consclose,
+	consread,
+	devbread,
+	conswrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+static	ulong	randn;
+
+static void
+seedrand(void)
+{
+	if(!waserror()){
+		randomread((void*)&randn, sizeof(randn));
+		poperror();
+	}
+}
+
+int
+nrand(int n)
+{
+	if(randn == 0)
+		seedrand();
+	randn = randn*1103515245 + 12345 + MACHP(0)->ticks;
+	return (randn>>16) % n;
+}
+
+int
+rand(void)
+{
+	nrand(1);
+	return randn;
+}
+
+static uvlong uvorder = 0x0001020304050607ULL;
+
+static uchar*
+le2vlong(vlong *to, uchar *f)
+{
+	uchar *t, *o;
+	int i;
+
+	t = (uchar*)to;
+	o = (uchar*)&uvorder;
+	for(i = 0; i < sizeof(vlong); i++)
+		t[o[i]] = f[i];
+	return f+sizeof(vlong);
+}
+
+static uchar*
+vlong2le(uchar *t, vlong from)
+{
+	uchar *f, *o;
+	int i;
+
+	f = (uchar*)&from;
+	o = (uchar*)&uvorder;
+	for(i = 0; i < sizeof(vlong); i++)
+		t[i] = f[o[i]];
+	return t+sizeof(vlong);
+}
+
+static long order = 0x00010203;
+
+static uchar*
+le2long(long *to, uchar *f)
+{
+	uchar *t, *o;
+	int i;
+
+	t = (uchar*)to;
+	o = (uchar*)&order;
+	for(i = 0; i < sizeof(long); i++)
+		t[o[i]] = f[i];
+	return f+sizeof(long);
+}
+
+static uchar*
+long2le(uchar *t, long from)
+{
+	uchar *f, *o;
+	int i;
+
+	f = (uchar*)&from;
+	o = (uchar*)&order;
+	for(i = 0; i < sizeof(long); i++)
+		t[i] = f[o[i]];
+	return t+sizeof(long);
+}
+
+char *Ebadtimectl = "bad time control";
+
+/*
+ *  like the old #c/time but with added info.  Return
+ *
+ *	secs	nanosecs	fastticks	fasthz
+ */
+static int
+readtime(ulong off, char *buf, int n)
+{
+	vlong	nsec, ticks;
+	long sec;
+	char str[7*NUMSIZE];
+
+	nsec = todget(&ticks);
+	if(fasthz == 0LL)
+		fastticks((uvlong*)&fasthz);
+	sec = nsec/1000000000ULL;
+	snprint(str, sizeof(str), "%*lud %*llud %*llud %*llud ",
+		NUMSIZE-1, sec,
+		VLNUMSIZE-1, nsec,
+		VLNUMSIZE-1, ticks,
+		VLNUMSIZE-1, fasthz);
+	return readstr(off, buf, n, str);
+}
+
+/*
+ *  set the time in seconds
+ */
+static int
+writetime(char *buf, int n)
+{
+	char b[13];
+	long i;
+	vlong now;
+
+	if(n >= sizeof(b))
+		error(Ebadtimectl);
+	strncpy(b, buf, n);
+	b[n] = 0;
+	i = strtol(b, 0, 0);
+	if(i <= 0)
+		error(Ebadtimectl);
+	now = i*1000000000LL;
+	todset(now, 0, 0);
+	return n;
+}
+
+/*
+ *  read binary time info.  all numbers are little endian.
+ *  ticks and nsec are syncronized.
+ */
+static int
+readbintime(char *buf, int n)
+{
+	int i;
+	vlong nsec, ticks;
+	uchar *b = (uchar*)buf;
+
+	i = 0;
+	if(fasthz == 0LL)
+		fastticks((uvlong*)&fasthz);
+	nsec = todget(&ticks);
+	if(n >= 3*sizeof(uvlong)){
+		vlong2le(b+2*sizeof(uvlong), fasthz);
+		i += sizeof(uvlong);
+	}
+	if(n >= 2*sizeof(uvlong)){
+		vlong2le(b+sizeof(uvlong), ticks);
+		i += sizeof(uvlong);
+	}
+	if(n >= 8){
+		vlong2le(b, nsec);
+		i += sizeof(vlong);
+	}
+	return i;
+}
+
+/*
+ *  set any of the following
+ *	- time in nsec
+ *	- nsec trim applied over some seconds
+ *	- clock frequency
+ */
+static int
+writebintime(char *buf, int n)
+{
+	uchar *p;
+	vlong delta;
+	long period;
+
+	n--;
+	p = (uchar*)buf + 1;
+	switch(*buf){
+	case 'n':
+		if(n < sizeof(vlong))
+			error(Ebadtimectl);
+		le2vlong(&delta, p);
+		todset(delta, 0, 0);
+		break;
+	case 'd':
+		if(n < sizeof(vlong)+sizeof(long))
+			error(Ebadtimectl);
+		p = le2vlong(&delta, p);
+		le2long(&period, p);
+		todset(-1, delta, period);
+		break;
+	case 'f':
+		if(n < sizeof(uvlong))
+			error(Ebadtimectl);
+		le2vlong(&fasthz, p);
+		if(fasthz <= 0)
+			error(Ebadtimectl);
+		todsetfreq(fasthz);
+		break;
+	}
+	return n;
+}

+ 530 - 0
sys/src/9/omap/devether.c

@@ -0,0 +1,530 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "../port/netif.h"
+#include "etherif.h"
+
+extern int archether(unsigned ctlno, Ether *ether);
+
+static Ether *etherxx[MaxEther];
+
+Chan*
+etherattach(char* spec)
+{
+	int ctlrno;
+	char *p;
+	Chan *chan;
+
+	ctlrno = 0;
+	if(spec && *spec){
+		ctlrno = strtoul(spec, &p, 0);
+		if((ctlrno == 0 && p == spec) || *p != 0)
+			error(Ebadarg);
+		if(ctlrno < 0 || ctlrno >= MaxEther)
+			error(Ebadarg);
+	}
+	if(etherxx[ctlrno] == 0)
+		error(Enodev);
+
+	chan = devattach('l', spec);
+	if(waserror()){
+		chanfree(chan);
+		nexterror();
+	}
+	chan->dev = ctlrno;
+	if(etherxx[ctlrno]->attach)
+		etherxx[ctlrno]->attach(etherxx[ctlrno]);
+	poperror();
+	return chan;
+}
+
+static Walkqid*
+etherwalk(Chan* chan, Chan* nchan, char** name, int nname)
+{
+	return netifwalk(etherxx[chan->dev], chan, nchan, name, nname);
+}
+
+static int
+etherstat(Chan* chan, uchar* dp, int n)
+{
+	return netifstat(etherxx[chan->dev], chan, dp, n);
+}
+
+static Chan*
+etheropen(Chan* chan, int omode)
+{
+	return netifopen(etherxx[chan->dev], chan, omode);
+}
+
+static void
+ethercreate(Chan*, char*, int, ulong)
+{
+}
+
+static void
+etherclose(Chan* chan)
+{
+	netifclose(etherxx[chan->dev], chan);
+}
+
+static long
+etherread(Chan* chan, void* buf, long n, vlong off)
+{
+	Ether *ether;
+	ulong offset = off;
+
+	ether = etherxx[chan->dev];
+	if((chan->qid.type & QTDIR) == 0 && ether->ifstat){
+		/*
+		 * With some controllers it is necessary to reach
+		 * into the chip to extract statistics.
+		 */
+		if(NETTYPE(chan->qid.path) == Nifstatqid)
+			return ether->ifstat(ether, buf, n, offset);
+		else if(NETTYPE(chan->qid.path) == Nstatqid)
+			ether->ifstat(ether, buf, 0, offset);
+	}
+
+	return netifread(ether, chan, buf, n, offset);
+}
+
+static Block*
+etherbread(Chan* chan, long n, ulong offset)
+{
+	return netifbread(etherxx[chan->dev], chan, n, offset);
+}
+
+static int
+etherwstat(Chan* chan, uchar* dp, int n)
+{
+	return netifwstat(etherxx[chan->dev], chan, dp, n);
+}
+
+static void
+etherrtrace(Netfile* f, Etherpkt* pkt, int len)
+{
+	int i, n;
+	Block *bp;
+
+	if(qwindow(f->in) <= 0)
+		return;
+	if(len > 58)
+		n = 58;
+	else
+		n = len;
+	bp = iallocb(64);
+	if(bp == nil)
+		return;
+	memmove(bp->wp, pkt->d, n);
+	i = TK2MS(MACHP(0)->ticks);
+	bp->wp[58] = len>>8;
+	bp->wp[59] = len;
+	bp->wp[60] = i>>24;
+	bp->wp[61] = i>>16;
+	bp->wp[62] = i>>8;
+	bp->wp[63] = i;
+	bp->wp += 64;
+	qpass(f->in, bp);
+}
+
+Block*
+etheriq(Ether* ether, Block* bp, int fromwire)
+{
+	Etherpkt *pkt;
+	ushort type;
+	int len, multi, tome, fromme;
+	Netfile **ep, *f, **fp, *fx;
+	Block *xbp;
+
+	ether->inpackets++;
+
+	pkt = (Etherpkt*)bp->rp;
+	len = BLEN(bp);
+	type = (pkt->type[0]<<8)|pkt->type[1];
+	fx = 0;
+	ep = &ether->f[Ntypes];
+
+	multi = pkt->d[0] & 1;
+	/* check for valid multicast addresses */
+	if(multi && memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) != 0 &&
+	    ether->prom == 0){
+		if(!activemulti(ether, pkt->d, sizeof(pkt->d))){
+			if(fromwire){
+				freeb(bp);
+				bp = 0;
+			}
+			return bp;
+		}
+	}
+	/* is it for me? */
+	tome = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0;
+	fromme = memcmp(pkt->s, ether->ea, sizeof(pkt->s)) == 0;
+
+	/*
+	 * Multiplex the packet to all the connections which want it.
+	 * If the packet is not to be used subsequently (fromwire != 0),
+	 * attempt to simply pass it into one of the connections, thereby
+	 * saving a copy of the data (usual case hopefully).
+	 */
+	for(fp = ether->f; fp < ep; fp++){
+		if((f = *fp) != nil && (f->type == type || f->type < 0) &&
+		    (tome || multi || f->prom)){
+			/* Don't want to hear bridged packets */
+			if(f->bridge && !fromwire && !fromme)
+				continue;
+			if(!f->headersonly){
+				if(fromwire && fx == 0)
+					fx = f;
+				else if(xbp = iallocb(len)){
+					memmove(xbp->wp, pkt, len);
+					xbp->wp += len;
+					if(qpass(f->in, xbp) < 0)
+						ether->soverflows++;
+				}
+				else
+					ether->soverflows++;
+			}
+			else
+				etherrtrace(f, pkt, len);
+		}
+	}
+
+	if(fx){
+		if(qpass(fx->in, bp) < 0)
+			ether->soverflows++;
+		return 0;
+	}
+	if(fromwire){
+		freeb(bp);
+		return 0;
+	}
+	return bp;
+}
+
+static int
+etheroq(Ether* ether, Block* bp)
+{
+	int len, loopback, s;
+	Etherpkt *pkt;
+
+	ether->outpackets++;
+
+	/*
+	 * Check if the packet has to be placed back onto the input queue,
+	 * i.e. if it's a loopback or broadcast packet or the interface is
+	 * in promiscuous mode.
+	 * If it's a loopback packet indicate to etheriq that the data isn't
+	 * needed and return, etheriq will pass-on or free the block.
+	 * To enable bridging to work, only packets that were originated
+	 * by this interface are fed back.
+	 */
+	pkt = (Etherpkt*)bp->rp;
+	len = BLEN(bp);
+	loopback = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0;
+	if(loopback || memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) == 0 || ether->prom){
+		s = splhi();
+		etheriq(ether, bp, 0);
+		splx(s);
+	}
+
+	if(!loopback){
+		qbwrite(ether->oq, bp);
+		if(ether->transmit != nil)
+			ether->transmit(ether);
+	} else
+		freeb(bp);
+
+	return len;
+}
+
+static long
+etherwrite(Chan* chan, void* buf, long n, vlong)
+{
+	Ether *ether;
+	Block *bp;
+	int nn, onoff;
+	Cmdbuf *cb;
+
+	ether = etherxx[chan->dev];
+	if(NETTYPE(chan->qid.path) != Ndataqid) {
+		nn = netifwrite(ether, chan, buf, n);
+		if(nn >= 0)
+			return nn;
+		cb = parsecmd(buf, n);
+		if(cb->f[0] && strcmp(cb->f[0], "nonblocking") == 0){
+			if(cb->nf <= 1)
+				onoff = 1;
+			else
+				onoff = atoi(cb->f[1]);
+			qnoblock(ether->oq, onoff);
+			free(cb);
+			return n;
+		}
+		free(cb);
+		if(ether->ctl!=nil)
+			return ether->ctl(ether,buf,n);
+			
+		error(Ebadctl);
+	}
+
+	if(n > ether->maxmtu)
+		error(Etoobig);
+	if(n < ether->minmtu)
+		error(Etoosmall);
+
+	bp = allocb(n);
+	if(waserror()){
+		freeb(bp);
+		nexterror();
+	}
+	memmove(bp->rp, buf, n);
+	memmove(bp->rp+Eaddrlen, ether->ea, Eaddrlen);
+	poperror();
+	bp->wp += n;
+
+	return etheroq(ether, bp);
+}
+
+static long
+etherbwrite(Chan* chan, Block* bp, ulong)
+{
+	Ether *ether;
+	long n;
+
+	n = BLEN(bp);
+	if(NETTYPE(chan->qid.path) != Ndataqid){
+		if(waserror()) {
+			freeb(bp);
+			nexterror();
+		}
+		n = etherwrite(chan, bp->rp, n, 0);
+		poperror();
+		freeb(bp);
+		return n;
+	}
+	ether = etherxx[chan->dev];
+
+	if(n > ether->maxmtu){
+		freeb(bp);
+		error(Etoobig);
+	}
+	if(n < ether->minmtu){
+		freeb(bp);
+		error(Etoosmall);
+	}
+
+	return etheroq(ether, bp);
+}
+
+static struct {
+	char*	type;
+	int	(*reset)(Ether*);
+} cards[MaxEther+1];
+
+void
+addethercard(char* t, int (*r)(Ether*))
+{
+	static int ncard;
+
+	if(ncard == MaxEther)
+		panic("too many ether cards");
+	cards[ncard].type = t;
+	cards[ncard].reset = r;
+	ncard++;
+}
+
+int
+parseether(uchar *to, char *from)
+{
+	char nip[4];
+	char *p;
+	int i;
+
+	p = from;
+	for(i = 0; i < Eaddrlen; i++){
+		if(*p == 0)
+			return -1;
+		nip[0] = *p++;
+		if(*p == 0)
+			return -1;
+		nip[1] = *p++;
+		nip[2] = 0;
+		to[i] = strtoul(nip, 0, 16);
+		if(*p == ':')
+			p++;
+	}
+	return 0;
+}
+
+static void
+etherreset(void)
+{
+	Ether *ether;
+	int i, n, ctlrno;
+	char name[KNAMELEN], buf[128];
+
+	for(ether = 0, ctlrno = 0; ctlrno < MaxEther; ctlrno++){
+		if(ether == 0)
+			ether = malloc(sizeof(Ether));
+		memset(ether, 0, sizeof(Ether));
+		ether->ctlrno = ctlrno;
+		ether->mbps = 10;
+		ether->minmtu = ETHERMINTU;
+		ether->maxmtu = ETHERMAXTU;
+
+		if(archether(ctlrno, ether) <= 0)
+			continue;
+
+		if(isaconfig("ether", ctlrno, ether) == 0){
+			free(ether);
+//			return nil;
+			continue;
+		}
+		for(n = 0; cards[n].type; n++){
+			if(cistrcmp(cards[n].type, ether->type))
+				continue;
+			for(i = 0; i < ether->nopt; i++)
+				if(cistrncmp(ether->opt[i], "ea=", 3) == 0){
+					if(parseether(ether->ea,
+					    &ether->opt[i][3]) == -1)
+						memset(ether->ea, 0, Eaddrlen);
+				} else if(cistrcmp(ether->opt[i],
+				    "100BASE-TXFD") == 0)
+					ether->mbps = 100;
+			if(cards[n].reset(ether))
+				break;
+			snprint(name, sizeof(name), "ether%d", ctlrno);
+
+			if(ether->interrupt != nil && ether->irq >= 0)
+				intrenable(ether->irq, ether->interrupt,
+					ether, 0, name);
+
+			i = snprint(buf, sizeof buf,
+				"#l%d: %s: %dMbps port %#lux irq %d",
+				ctlrno, ether->type, ether->mbps, ether->port,
+				ether->irq);
+			if(ether->mem)
+				i += snprint(buf+i, sizeof buf - i,
+					" addr %#lux", PADDR(ether->mem));
+			if(ether->size)
+				i += snprint(buf+i, sizeof buf - i,
+					" size 0x%luX", ether->size);
+			i += snprint(buf+i, sizeof buf - i,
+				": %2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
+				ether->ea[0], ether->ea[1], ether->ea[2],
+				ether->ea[3], ether->ea[4], ether->ea[5]);
+			snprint(buf+i, sizeof buf - i, "\n");
+			iprint("%s", buf);  /* it may be too early for print */
+
+			if(ether->mbps >= 1000)
+				netifinit(ether, name, Ntypes, 4*1024*1024);
+			else if(ether->mbps >= 100)
+				netifinit(ether, name, Ntypes, 1024*1024);
+			else
+				netifinit(ether, name, Ntypes, 65*1024);
+			if(ether->oq == 0)
+				ether->oq = qopen(ether->limit, Qmsg, 0, 0);
+			if(ether->oq == 0)
+				panic("etherreset %s", name);
+			ether->alen = Eaddrlen;
+			memmove(ether->addr, ether->ea, Eaddrlen);
+			memset(ether->bcast, 0xFF, Eaddrlen);
+
+			etherxx[ctlrno] = ether;
+			ether = 0;
+			break;
+		}
+	}
+	if(ether)
+		free(ether);
+}
+
+static void
+ethershutdown(void)
+{
+	Ether *ether;
+	int i;
+
+	for(i = 0; i < MaxEther; i++){
+		ether = etherxx[i];
+		if(ether == nil)
+			continue;
+		if(ether->shutdown == nil) {
+			print("#l%d: no shutdown function\n", i);
+			continue;
+		}
+		(*ether->shutdown)(ether);
+	}
+}
+
+
+#define POLY 0xedb88320
+
+/* really slow 32 bit crc for ethers */
+ulong
+ethercrc(uchar *p, int len)
+{
+	int i, j;
+	ulong crc, b;
+
+	crc = 0xffffffff;
+	for(i = 0; i < len; i++){
+		b = *p++;
+		for(j = 0; j < 8; j++){
+			crc = (crc>>1) ^ (((crc^b) & 1) ? POLY : 0);
+			b >>= 1;
+		}
+	}
+	return crc;
+}
+
+void
+dumpoq(Queue *oq)
+{
+	if (oq == nil)
+		print("no outq! ");
+	else if (qisclosed(oq))
+		print("outq closed ");
+	else if (qfull(oq))
+		print("outq full ");
+	else
+		print("outq %d ", qlen(oq));
+}
+
+void
+dumpnetif(Netif *netif)
+{
+	print("netif %s ", netif->name);
+	print("limit %d mbps %d link %d ",
+		netif->limit, netif->mbps, netif->link);
+	print("inpkts %lld outpkts %lld errs %d\n",
+		netif->inpackets, netif->outpackets,
+		netif->crcs + netif->oerrs + netif->frames + netif->overflows +
+		netif->buffs + netif->soverflows);
+}
+
+Dev etherdevtab = {
+	'l',
+	"ether",
+
+	etherreset,
+	devinit,
+	ethershutdown,
+	etherattach,
+	etherwalk,
+	etherstat,
+	etheropen,
+	ethercreate,
+	etherclose,
+	etherread,
+	etherbread,
+	etherwrite,
+	etherbwrite,
+	devremove,
+	etherwstat,
+};

+ 1473 - 0
sys/src/9/omap/devusb.c

@@ -0,0 +1,1473 @@
+/*
+ * USB device driver.
+ *
+ * This is in charge of providing access to actual HCIs
+ * and providing I/O to the various endpoints of devices.
+ * A separate user program (usbd) is in charge of
+ * enumerating the bus, setting up endpoints and
+ * starting devices (also user programs).
+ *
+ * The interface provided is a violation of the standard:
+ * you're welcome.
+ *
+ * The interface consists of a root directory with several files
+ * plus a directory (epN.M) with two files per endpoint.
+ * A device is represented by its first endpoint, which
+ * is a control endpoint automatically allocated for each device.
+ * Device control endpoints may be used to create new endpoints.
+ * Devices corresponding to hubs may also allocate new devices,
+ * perhaps also hubs. Initially, a hub device is allocated for
+ * each controller present, to represent its root hub. Those can
+ * never be removed.
+ *
+ * All endpoints refer to the first endpoint (epN.0) of the device,
+ * which keeps per-device information, and also to the HCI used
+ * to reach them. Although all endpoints cache that information.
+ *
+ * epN.M/data files permit I/O and are considered DMEXCL.
+ * epN.M/ctl files provide status info and accept control requests.
+ *
+ * Endpoints may be given file names to be listed also at #u,
+ * for those drivers that have nothing to do after configuring the
+ * device and its endpoints.
+ *
+ * Drivers for different controllers are kept at usb[oue]hci.c
+ * It's likely we could factor out much from controllers into
+ * a generic controller driver, the problem is that details
+ * regarding how to handle toggles, tokens, Tds, etc. will
+ * get in the way. Thus, code is probably easier the way it is.
+ *
+ */
+
+#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"
+
+typedef struct Hcitype Hcitype;
+
+enum
+{
+	/* Qid numbers */
+	Qdir = 0,		/* #u */
+	Qusbdir,			/* #u/usb */
+	Qctl,			/* #u/usb/ctl - control requests */
+
+	Qep0dir,			/* #u/usb/ep0.0 - endpoint 0 dir */
+	Qep0io,			/* #u/usb/ep0.0/data - endpoint 0 I/O */
+	Qep0ctl,		/* #u/usb/ep0.0/ctl - endpoint 0 ctl. */
+	Qep0dummy,		/* give 4 qids to each endpoint */
+
+	Qepdir = 0,		/* (qid-qep0dir)&3 is one of these */
+	Qepio,			/* to identify which file for the endpoint */
+	Qepctl,
+
+	/* ... */
+
+	/* Usb ctls. */
+	CMdebug = 0,		/* debug on|off */
+	CMdump,			/* dump (data structures for debug) */
+
+	/* Ep. ctls */
+	CMnew = 0,		/* new nb ctl|bulk|intr|iso r|w|rw (endpoint) */
+	CMnewdev,		/* newdev full|low|high portnb (allocate new devices) */
+	CMhub,			/* hub (set the device as a hub) */
+	CMspeed,		/* speed full|low|high|no */
+	CMmaxpkt,		/* maxpkt size */
+	CMntds,			/* ntds nb (max nb. of tds per µframe) */
+	CMclrhalt,		/* clrhalt (halt was cleared on endpoint) */
+	CMpollival,		/* pollival interval (interrupt/iso) */
+	CMhz,			/* hz n (samples/sec; iso) */
+	CMsamplesz,		/* samplesz n (sample size; iso) */
+	CMinfo,			/* info infostr (ke.ep info for humans) */
+	CMdetach,		/* detach (abort I/O forever on this ep). */
+	CMaddress,		/* address (address is assigned) */
+	CMdebugep,		/* debug n (set/clear debug for this ep) */
+	CMname,			/* name str (show up as #u/name as well) */
+	CMtmout,		/* timeout n (activate timeouts for ep) */
+	CMpreset,		/* reset the port */
+
+	/* Hub feature selectors */
+	Rportenable	= 1,
+	Rportreset	= 4,
+
+};
+
+struct Hcitype
+{
+	char*	type;
+	int	(*reset)(Hci*);
+};
+
+#define QID(q)	((int)(q).path)
+
+static char Edetach[] = "device is detached";
+static char Enotconf[] = "endpoint not configured";
+char Estalled[] = "endpoint stalled";
+
+static Cmdtab usbctls[] =
+{
+	{CMdebug,	"debug",	2},
+	{CMdump,	"dump",		1},
+};
+
+static Cmdtab epctls[] =
+{
+	{CMnew,		"new",		4},
+	{CMnewdev,	"newdev",	3},
+	{CMhub,		"hub",		1},
+	{CMspeed,	"speed",	2},
+	{CMmaxpkt,	"maxpkt",	2},
+	{CMntds,	"ntds",		2},
+	{CMpollival,	"pollival",	2},
+	{CMsamplesz,	"samplesz",	2},
+	{CMhz,		"hz",		2},
+	{CMinfo,		"info",		0},
+	{CMdetach,	"detach",	1},
+	{CMaddress,	"address",	1},
+	{CMdebugep,	"debug",	2},
+	{CMclrhalt,	"clrhalt",	1},
+	{CMname,	"name",		2},
+	{CMtmout,	"timeout",	2},
+	{CMpreset,	"reset",	1},
+};
+
+static Dirtab usbdir[] =
+{
+	"ctl",		{Qctl},		0,	0666,
+};
+
+char *usbmodename[] =
+{
+	[OREAD]	"r",
+	[OWRITE]	"w",
+	[ORDWR]	"rw",
+};
+
+static char *ttname[] =
+{
+	[Tnone]	"none",
+	[Tctl]	"control",
+	[Tiso]	"iso",
+	[Tintr]	"interrupt",
+	[Tbulk]	"bulk",
+};
+
+static char *spname[] =
+{
+	[Fullspeed]	"full",
+	[Lowspeed]	"low",
+	[Highspeed]	"high",
+	[Nospeed]	"no",
+};
+
+static int	debug = 0;
+static Hcitype	hcitypes[Nhcis];
+static Hci*	hcis[Nhcis];
+static QLock	epslck;		/* add, del, lookup endpoints */
+static Ep*	eps[Neps];	/* all endpoints known */
+static int	epmax;		/* 1 + last endpoint index used  */
+static int	usbidgen;	/* device address generator */
+
+/*
+ * Is there something like this in a library? should it be?
+ */
+char*
+seprintdata(char *s, char *se, uchar *d, int n)
+{
+	int i, l;
+
+	s = seprint(s, se, " %#p[%d]: ", d, n);
+	l = n;
+	if(l > 10)
+		l = 10;
+	for(i=0; i<l; i++)
+		s = seprint(s, se, " %2.2ux", d[i]);
+	if(l < n)
+		s = seprint(s, se, "...");
+	return s;
+}
+
+static int
+name2speed(char *name)
+{
+	int i;
+
+	for(i = 0; i < nelem(spname); i++)
+		if(strcmp(name, spname[i]) == 0)
+			return i;
+	return Nospeed;
+}
+
+static int
+name2ttype(char *name)
+{
+	int i;
+
+	for(i = 0; i < nelem(ttname); i++)
+		if(strcmp(name, ttname[i]) == 0)
+			return i;
+	/* may be a std. USB ep. type */
+	i = strtol(name, nil, 0);
+	switch(i+1){
+	case Tctl:
+	case Tiso:
+	case Tbulk:
+	case Tintr:
+		return i+1;
+	default:
+		return Tnone;
+	}
+}
+
+static int
+name2mode(char *mode)
+{
+	int i;
+
+	for(i = 0; i < nelem(usbmodename); i++)
+		if(strcmp(mode, usbmodename[i]) == 0)
+			return i;
+	return -1;
+}
+
+static int
+qid2epidx(int q)
+{
+	q = (q-Qep0dir)/4;
+	if(q < 0 || q >= epmax || eps[q] == nil)
+		return -1;
+	return q;
+}
+
+static int
+isqtype(int q, int type)
+{
+	if(q < Qep0dir)
+		return 0;
+	q -= Qep0dir;
+	return (q & 3) == type;
+}
+
+void
+addhcitype(char* t, int (*r)(Hci*))
+{
+	static int ntype;
+
+	if(ntype == Nhcis)
+		panic("too many USB host interface types");
+	hcitypes[ntype].type = t;
+	hcitypes[ntype].reset = r;
+	ntype++;
+}
+
+static char*
+seprintep(char *s, char *se, Ep *ep, int all)
+{
+	static char* dsnames[] = { "config", "enabled", "detached", "reset" };
+	Udev *d;
+	int i;
+	int di;
+
+	d = ep->dev;
+
+	qlock(ep);
+	if(waserror()){
+		qunlock(ep);
+		nexterror();
+	}
+	di = ep->dev->nb;
+	if(all)
+		s = seprint(s, se, "dev %d ep %d ", di, ep->nb);
+	s = seprint(s, se, "%s", dsnames[ep->dev->state]);
+	s = seprint(s, se, " %s", ttname[ep->ttype]);
+	assert(ep->mode == OREAD || ep->mode == OWRITE || ep->mode == ORDWR);
+	s = seprint(s, se, " %s", usbmodename[ep->mode]);
+	s = seprint(s, se, " speed %s", spname[d->speed]);
+	s = seprint(s, se, " maxpkt %ld", ep->maxpkt);
+	s = seprint(s, se, " pollival %ld", ep->pollival);
+	s = seprint(s, se, " samplesz %ld", ep->samplesz);
+	s = seprint(s, se, " hz %ld", ep->hz);
+	s = seprint(s, se, " hub %d", ep->dev->hub);
+	s = seprint(s, se, " port %d", ep->dev->port);
+	if(ep->inuse)
+		s = seprint(s, se, " busy");
+	else
+		s = seprint(s, se, " idle");
+	if(all){
+		s = seprint(s, se, " load %uld", ep->load);
+		s = seprint(s, se, " ref %ld addr %#p", ep->ref, ep);
+		s = seprint(s, se, " idx %d", ep->idx);
+		if(ep->name != nil)
+			s = seprint(s, se, " name '%s'", ep->name);
+		if(ep->tmout != 0)
+			s = seprint(s, se, " tmout");
+		if(ep == ep->ep0){
+			s = seprint(s, se, " ctlrno %#x", ep->hp->ctlrno);
+			s = seprint(s, se, " eps:");
+			for(i = 0; i < nelem(d->eps); i++)
+				if(d->eps[i] != nil)
+					s = seprint(s, se, " ep%d.%d", di, i);
+		}
+	}
+	if(ep->info != nil)
+		s = seprint(s, se, "\n%s %s\n", ep->info, ep->hp->type);
+	else
+		s = seprint(s, se, "\n");
+	qunlock(ep);
+	poperror();
+	return s;
+}
+
+static Ep*
+epalloc(Hci *hp)
+{
+	Ep *ep;
+	int i;
+
+	ep = mallocz(sizeof(Ep), 1);
+	ep->ref = 1;
+	qlock(&epslck);
+	for(i = 0; i < Neps; i++)
+		if(eps[i] == nil)
+			break;
+	if(i == Neps){
+		qunlock(&epslck);
+		free(ep);
+		print("usb: bug: too few endpoints.\n");
+		return nil;
+	}
+	ep->idx = i;
+	if(epmax <= i)
+		epmax = i+1;
+	eps[i] = ep;
+	ep->hp = hp;
+	ep->maxpkt = 8;
+	ep->ntds = 1;
+	ep->samplesz = ep->pollival = ep->hz = 0; /* make them void */
+	qunlock(&epslck);
+	return ep;
+}
+
+static Ep*
+getep(int i)
+{
+	Ep *ep;
+
+	if(i < 0 || i >= epmax || eps[i] == nil)
+		return nil;
+	qlock(&epslck);
+	ep = eps[i];
+	if(ep != nil)
+		incref(ep);
+	qunlock(&epslck);
+	return ep;
+}
+
+static void
+putep(Ep *ep)
+{
+	Udev *d;
+
+	if(ep != nil && decref(ep) == 0){
+		d = ep->dev;
+		deprint("usb: ep%d.%d %#p released\n", d->nb, ep->nb, ep);
+		qlock(&epslck);
+		eps[ep->idx] = nil;
+		if(ep->idx == epmax-1)
+			epmax--;
+		if(ep == ep->ep0 && ep->dev != nil && ep->dev->nb == usbidgen)
+			usbidgen--;
+		qunlock(&epslck);
+		if(d != nil){
+			qlock(ep->ep0);
+			d->eps[ep->nb] = nil;
+			qunlock(ep->ep0);
+		}
+		if(ep->ep0 != ep){
+			putep(ep->ep0);
+			ep->ep0 = nil;
+		}
+		free(ep->info);
+		free(ep->name);
+		free(ep);
+	}
+}
+
+static void
+dumpeps(void)
+{
+	int i;
+	static char buf[512];
+	char *s;
+	char *e;
+	Ep *ep;
+
+	print("usb dump eps: epmax %d Neps %d (ref=1+ for dump):\n", epmax, Neps);
+	for(i = 0; i < epmax; i++){
+		s = buf;
+		e = buf+sizeof(buf);
+		ep = getep(i);
+		if(ep != nil){
+			if(waserror()){
+				putep(ep);
+				nexterror();
+			}
+			s = seprint(s, e, "ep%d.%d ", ep->dev->nb, ep->nb);
+			seprintep(s, e, ep, 1);
+			print("%s", buf);
+			ep->hp->seprintep(buf, e, ep);
+			print("%s", buf);
+			poperror();
+			putep(ep);
+		}
+	}
+	print("usb dump hcis:\n");
+	for(i = 0; i < Nhcis; i++)
+		if(hcis[i] != nil)
+			hcis[i]->dump(hcis[i]);
+}
+
+static int
+newusbid(Hci *)
+{
+	int id;
+
+	qlock(&epslck);
+	id = ++usbidgen;
+	if(id >= 0x7F)
+		print("#u: too many device addresses; reuse them more\n");
+	qunlock(&epslck);
+	return id;
+}
+
+/*
+ * Create endpoint 0 for a new device
+ */
+static Ep*
+newdev(Hci *hp, int ishub, int isroot)
+{
+	Ep *ep;
+	Udev *d;
+
+	ep = epalloc(hp);
+	d = ep->dev = mallocz(sizeof(Udev), 1);
+	d->nb = newusbid(hp);
+	d->eps[0] = ep;
+	ep->nb = 0;
+	ep->toggle[0] = ep->toggle[1] = 0;
+	d->ishub = ishub;
+	d->isroot = isroot;
+	if(hp->highspeed != 0)
+		d->speed = Highspeed;
+	else
+		d->speed = Fullspeed;
+	d->state = Dconfig;		/* address not yet set */
+	ep->dev = d;
+	ep->ep0 = ep;			/* no ref counted here */
+	ep->ttype = Tctl;
+	ep->tmout = Xfertmout;
+	ep->mode = ORDWR;
+	dprint("newdev %#p ep%d.%d %#p\n", d, d->nb, ep->nb, ep);
+	return ep;
+}
+
+/*
+ * Create a new endpoint for the device
+ * accessed via the given endpoint 0.
+ */
+static Ep*
+newdevep(Ep *ep, int i, int tt, int mode)
+{
+	Ep *nep;
+	Udev *d;
+
+	d = ep->dev;
+	if(d->eps[i] != nil)
+		error("endpoint already in use");
+	nep = epalloc(ep->hp);
+	incref(ep);
+	d->eps[i] = nep;
+	nep->nb = i;
+	nep->toggle[0] = nep->toggle[1] = 0;
+	nep->ep0 = ep;
+	nep->dev = ep->dev;
+	nep->mode = mode;
+	nep->ttype = tt;
+	nep->debug = ep->debug;
+	/* set defaults */
+	switch(tt){
+	case Tctl:
+		nep->tmout = Xfertmout;
+		break;
+	case Tintr:
+		nep->pollival = 10;
+		break;
+	case Tiso:
+		nep->tmout = Xfertmout;
+		nep->pollival = 10;
+		nep->samplesz = 4;
+		nep->hz = 44100;
+		break;
+	}
+	deprint("newdevep ep%d.%d %#p\n", d->nb, nep->nb, nep);
+	return ep;
+}
+
+static int
+epdataperm(int mode)
+{
+
+	switch(mode){
+	case OREAD:
+		return 0440|DMEXCL;
+		break;
+	case OWRITE:
+		return 0220|DMEXCL;
+		break;
+	default:
+		return 0660|DMEXCL;
+	}
+}
+
+static int
+usbgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
+{
+	Qid q;
+	Dirtab *dir;
+	int perm;
+	char *se;
+	Ep *ep;
+	int nb;
+	int mode;
+
+	if(0)ddprint("usbgen q %#x s %d...", QID(c->qid), s);
+	if(s == DEVDOTDOT){
+		if(QID(c->qid) <= Qusbdir){
+			mkqid(&q, Qdir, 0, QTDIR);
+			devdir(c, q, "#u", 0, eve, 0555, dp);
+		}else{
+			mkqid(&q, Qusbdir, 0, QTDIR);
+			devdir(c, q, "usb", 0, eve, 0555, dp);
+		}
+		if(0)ddprint("ok\n");
+		return 1;
+	}
+
+	switch(QID(c->qid)){
+	case Qdir:				/* list #u */
+		if(s == 0){
+			mkqid(&q, Qusbdir, 0, QTDIR);
+			devdir(c, q, "usb", 0, eve, 0555, dp);
+			if(0)ddprint("ok\n");
+			return 1;
+		}
+		s--;
+		if(s < 0 || s >= epmax)
+			goto Fail;
+		ep = getep(s);
+		if(ep == nil || ep->name == nil){
+			if(ep != nil)
+				putep(ep);
+			if(0)ddprint("skip\n");
+			return 0;
+		}
+		if(waserror()){
+			putep(ep);
+			nexterror();
+		}
+		mkqid(&q, Qep0io+s*4, 0, QTFILE);
+		devdir(c, q, ep->name, 0, eve, epdataperm(ep->mode), dp);
+		putep(ep);
+		poperror();
+		if(0)ddprint("ok\n");
+		return 1;
+
+	case Qusbdir:				/* list #u/usb */
+	Usbdir:
+		if(s < nelem(usbdir)){
+			dir = &usbdir[s];
+			mkqid(&q, dir->qid.path, 0, QTFILE);
+			devdir(c, q, dir->name, dir->length, eve, dir->perm, dp);
+			if(0)ddprint("ok\n");
+			return 1;
+		}
+		s -= nelem(usbdir);
+		if(s < 0 || s >= epmax)
+			goto Fail;
+		ep = getep(s);
+		if(ep == nil){
+			if(0)ddprint("skip\n");
+			return 0;
+		}
+		if(waserror()){
+			putep(ep);
+			nexterror();
+		}
+		se = up->genbuf+sizeof(up->genbuf);
+		seprint(up->genbuf, se, "ep%d.%d", ep->dev->nb, ep->nb);
+		mkqid(&q, Qep0dir+4*s, 0, QTDIR);
+		putep(ep);
+		poperror();
+		devdir(c, q, up->genbuf, 0, eve, 0755, dp);
+		if(0)ddprint("ok\n");
+		return 1;
+
+	case Qctl:
+		s = 0;
+		goto Usbdir;
+
+	default:				/* list #u/usb/epN.M */
+		nb = qid2epidx(QID(c->qid));
+		ep = getep(nb);
+		if(ep == nil)
+			goto Fail;
+		mode = ep->mode;
+		putep(ep);
+		if(isqtype(QID(c->qid), Qepdir)){
+		Epdir:
+			switch(s){
+			case 0:
+				mkqid(&q, Qep0io+nb*4, 0, QTFILE);
+				perm = epdataperm(mode);
+				devdir(c, q, "data", 0, eve, perm, dp);
+				break;
+			case 1:
+				mkqid(&q, Qep0ctl+nb*4, 0, QTFILE);
+				devdir(c, q, "ctl", 0, eve, 0664, dp);
+				break;
+			default:
+				goto Fail;
+			}
+		}else if(isqtype(QID(c->qid), Qepctl)){
+			s = 1;
+			goto Epdir;
+		}else{
+			s = 0;
+			goto Epdir;
+		}
+		if(0)ddprint("ok\n");
+		return 1;
+	}
+Fail:
+	if(0)ddprint("fail\n");
+	return -1;
+}
+
+static Hci*
+hciprobe(int cardno, int ctlrno)
+{
+	Hci *hp;
+	char *type;
+	char name[64];
+	static int epnb = 1;	/* guess the endpoint nb. for the controller */
+
+	ddprint("hciprobe %d %d\n", cardno, ctlrno);
+	hp = mallocz(sizeof(Hci), 1);
+	hp->ctlrno = ctlrno;
+
+	if(cardno < 0){
+		for(cardno = 0; cardno < Nhcis; cardno++){
+			if(hcitypes[cardno].type == nil)
+				break;
+			type = hp->type;
+			if(type==nil || *type==0)
+				type = "uhci";
+			if(cistrcmp(hcitypes[cardno].type, type) == 0)
+				break;
+		}
+	}
+
+	if(cardno >= Nhcis || hcitypes[cardno].type == nil){
+		free(hp);
+		return nil;
+	}
+	dprint("%s...", hcitypes[cardno].type);
+	if(hcitypes[cardno].reset(hp) < 0){
+		free(hp);
+		return nil;
+	}
+
+	snprint(name, sizeof(name), "usb%s", hcitypes[cardno].type);
+	intrenable(hp->irq, hp->interrupt, hp, UNKNOWN, name);
+
+	print("#u/usb/ep%d.0: %s: port 0x%luX irq %d\n",
+		epnb, hcitypes[cardno].type, hp->port, hp->irq);
+	epnb++;
+
+	return hp;
+}
+
+static void
+usbreset(void)
+{
+	int cardno, ctlrno;
+	Hci *hp;
+
+	dprint("usbreset\n");
+
+	for(ctlrno = 0; ctlrno < Nhcis; ctlrno++)
+		if((hp = hciprobe(-1, ctlrno)) != nil)
+			hcis[ctlrno] = hp;
+	cardno = ctlrno = 0;
+	while(cardno < Nhcis && ctlrno < Nhcis && hcitypes[cardno].type != nil)
+		if(hcis[ctlrno] != nil)
+			ctlrno++;
+		else{
+			hp = hciprobe(cardno, ctlrno);
+			if(hp == nil)
+				cardno++;
+			hcis[ctlrno++] = hp;
+		}
+	if(hcis[Nhcis-1] != nil)
+		print("usbreset: bug: Nhcis too small\n");
+}
+
+static void
+usbinit(void)
+{
+	Hci *hp;
+	int ctlrno;
+	Ep *d;
+	char info[40];
+
+	dprint("usbinit\n");
+	for(ctlrno = 0; ctlrno < Nhcis; ctlrno++){
+		hp = hcis[ctlrno];
+		if(hp != nil){
+			if(hp->init != nil)
+				hp->init(hp);
+			d = newdev(hp, 1, 1);		/* new root hub */
+			d->dev->state = Denabled;	/* although addr == 0 */
+			d->maxpkt = 64;
+			snprint(info, sizeof(info), "ports %d", hp->nports);
+			kstrdup(&d->info, info);
+		}
+	}
+}
+
+static Chan*
+usbattach(char *spec)
+{
+	return devattach(L'u', spec);
+}
+
+static Walkqid*
+usbwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, nil, 0, usbgen);
+}
+
+static int
+usbstat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, nil, 0, usbgen);
+}
+
+/*
+ * µs for the given transfer, for bandwidth allocation.
+ * This is a very rough worst case for what 5.11.3
+ * of the usb 2.0 spec says.
+ * Also, we are using maxpkt and not actual transfer sizes.
+ * Only when we are sure we
+ * are not exceeding b/w might we consider adjusting it.
+ */
+static ulong
+usbload(int speed, int maxpkt)
+{
+	enum{ Hostns = 1000, Hubns = 333 };
+	ulong l;
+	ulong bs;
+
+	l = 0;
+	bs = 10UL * maxpkt;
+	switch(speed){
+	case Highspeed:
+		l = 55*8*2 + 2 * (3 + bs) + Hostns;
+		break;
+	case Fullspeed:
+		l = 9107 + 84 * (4 + bs) + Hostns;
+		break;
+	case Lowspeed:
+		l = 64107 + 2 * Hubns + 667 * (3 + bs) + Hostns;
+		break;
+	default:
+		print("usbload: bad speed %d\n", speed);
+		/* let it run */
+	}
+	return l / 1000UL;	/* in µs */
+}
+
+static Chan*
+usbopen(Chan *c, int omode)
+{
+	int q;
+	Ep *ep;
+	int mode;
+
+	mode = openmode(omode);
+	q = QID(c->qid);
+
+	if(q >= Qep0dir && qid2epidx(q) < 0)
+		error(Eio);
+	if(q < Qep0dir || isqtype(q, Qepctl) || isqtype(q, Qepdir))
+		return devopen(c, omode, nil, 0, usbgen);
+
+	ep = getep(qid2epidx(q));
+	if(ep == nil)
+		error(Eio);
+	deprint("usbopen q %#x fid %d omode %d\n", q, c->fid, mode);
+	if(waserror()){
+		putep(ep);
+		nexterror();
+	}
+	qlock(ep);
+	if(ep->inuse){
+		qunlock(ep);
+		error(Einuse);
+	}
+	ep->inuse = 1;
+	qunlock(ep);
+	if(waserror()){
+		ep->inuse = 0;
+		nexterror();
+	}
+	if(mode != OREAD && ep->mode == OREAD)
+		error(Eperm);
+	if(mode != OWRITE && ep->mode == OWRITE)
+		error(Eperm);
+	if(ep->ttype == Tnone)
+		error(Enotconf);
+	ep->clrhalt = 0;
+	ep->rhrepl = -1;
+	if(ep->load == 0)
+		ep->load = usbload(ep->dev->speed, ep->maxpkt);
+	ep->hp->epopen(ep);
+
+	poperror();	/* ep->inuse */
+	poperror();	/* don't putep(): ref kept for fid using the ep. */
+
+	c->mode = mode;
+	c->flag |= COPEN;
+	c->offset = 0;
+	c->aux = nil;	/* paranoia */
+	return c;
+}
+
+static void
+epclose(Ep *ep)
+{
+	qlock(ep);
+	if(waserror()){
+		qunlock(ep);
+		nexterror();
+	}
+	if(ep->inuse){
+		ep->hp->epclose(ep);
+		ep->inuse = 0;
+	}
+	qunlock(ep);
+	poperror();
+}
+
+static void
+usbclose(Chan *c)
+{
+	int q;
+	Ep *ep;
+
+	q = QID(c->qid);
+	if(q < Qep0dir || isqtype(q, Qepctl) || isqtype(q, Qepdir))
+		return;
+
+	ep = getep(qid2epidx(q));
+	if(ep == nil)
+		return;
+	deprint("usbclose q %#x fid %d ref %ld\n", q, c->fid, ep->ref);
+	if(waserror()){
+		putep(ep);
+		nexterror();
+	}
+	if(c->flag & COPEN){
+		free(c->aux);
+		c->aux = nil;
+		epclose(ep);
+		putep(ep);	/* release ref kept since usbopen */
+		c->flag &= ~COPEN;
+	}
+	poperror();
+	putep(ep);
+}
+
+static long
+ctlread(Chan *c, void *a, long n, vlong offset)
+{
+	int q;
+	char *s;
+	char *us;
+	char *se;
+	Ep *ep;
+	int i;
+
+	q = QID(c->qid);
+	us = s = smalloc(READSTR);
+	se = s + READSTR;
+	if(waserror()){
+		free(us);
+		nexterror();
+	}
+	if(q == Qctl)
+		for(i = 0; i < epmax; i++){
+			ep = getep(i);
+			if(ep != nil){
+				if(waserror()){
+					putep(ep);
+					nexterror();
+				}
+				s = seprint(s, se, "ep%d.%d ", ep->dev->nb, ep->nb);
+				s = seprintep(s, se, ep, 0);
+				poperror();
+			}
+			putep(ep);
+		}
+	else{
+		ep = getep(qid2epidx(q));
+		if(ep == nil)
+			error(Eio);
+		if(waserror()){
+			putep(ep);
+			nexterror();
+		}
+		if(c->aux != nil){
+			/* After a new endpoint request we read
+			 * the new endpoint name back.
+			 */
+			strecpy(s, se, c->aux);
+			free(c->aux);
+			c->aux = nil;
+		}else
+			seprintep(s, se, ep, 0);
+		poperror();
+		putep(ep);
+	}
+	n = readstr(offset, a, n, us);
+	poperror();
+	free(us);
+	return n;
+}
+
+/*
+ * Fake root hub emulation.
+ */
+static long
+rhubread(Ep *ep, void *a, long n)
+{
+	char *b;
+
+	if(ep->dev->isroot == 0 || ep->nb != 0 || n < 2)
+		return -1;
+	if(ep->rhrepl < 0)
+		return -1;
+
+	b = a;
+	memset(b, 0, n);
+	PUT2(b, ep->rhrepl);
+	ep->rhrepl = -1;
+	return n;
+}
+
+static long
+rhubwrite(Ep *ep, void *a, long n)
+{
+	uchar *s;
+	int cmd;
+	int feature;
+	int port;
+	Hci *hp;
+
+	if(ep->dev == nil || ep->dev->isroot == 0 || ep->nb != 0)
+		return -1;
+	if(n != Rsetuplen)
+		error("root hub is a toy hub");
+	ep->rhrepl = -1;
+	s = a;
+	if(s[Rtype] != (Rh2d|Rclass|Rother) && s[Rtype] != (Rd2h|Rclass|Rother))
+		error("root hub is a toy hub");
+	hp = ep->hp;
+	cmd = s[Rreq];
+	feature = GET2(s+Rvalue);
+	port = GET2(s+Rindex);
+	if(port < 1 || port > hp->nports)
+		error("bad hub port number");
+	switch(feature){
+	case Rportenable:
+		ep->rhrepl = hp->portenable(hp, port, cmd == Rsetfeature);
+		break;
+	case Rportreset:
+		ep->rhrepl = hp->portreset(hp, port, cmd == Rsetfeature);
+		break;
+	case Rgetstatus:
+		ep->rhrepl = hp->portstatus(hp, port);
+		break;
+	default:
+		ep->rhrepl = 0;
+	}
+	return n;
+}
+
+static long
+usbread(Chan *c, void *a, long n, vlong offset)
+{
+	int q;
+	Ep *ep;
+	int nr;
+
+	q = QID(c->qid);
+
+	if(c->qid.type == QTDIR)
+		return devdirread(c, a, n, nil, 0, usbgen);
+
+	if(q == Qctl || isqtype(q, Qepctl))
+		return ctlread(c, a, n, offset);
+
+	ep = getep(qid2epidx(q));
+	if(ep == nil)
+		error(Eio);
+	if(waserror()){
+		putep(ep);
+		nexterror();
+	}
+	if(ep->dev->state == Ddetach)
+		error(Edetach);
+	if(ep->mode == OWRITE || ep->inuse == 0)
+		error(Ebadusefd);
+	switch(ep->ttype){
+	case Tnone:
+		error("endpoint not configured");
+	case Tctl:
+		nr = rhubread(ep, a, n);
+		if(nr >= 0){
+			n = nr;
+			break;
+		}
+		/* else fall */
+	default:
+		ddeprint("\nusbread q %#x fid %d cnt %ld off %lld\n",q,c->fid,n,offset);
+		n = ep->hp->epread(ep, a, n);
+		break;
+	}
+	poperror();
+	putep(ep);
+	return n;
+}
+
+static long
+pow2(int n)
+{
+	long v;
+
+	for(v = 1; n > 0; n--)
+		v *= 2;
+	return v;
+}
+
+static void
+setmaxpkt(Ep *ep, char* s)
+{
+	long spp;	/* samples per packet */
+
+	if(ep->dev->speed == Highspeed)
+		spp = (ep->hz * ep->pollival * ep->ntds + 7999) / 8000;
+	else
+		spp = (ep->hz * ep->pollival + 999) / 1000;
+	ep->maxpkt = spp * ep->samplesz;
+	deprint("usb: %s: setmaxpkt: hz %ld poll %ld"
+		" ntds %d %s speed -> spp %ld maxpkt %ld\n", s,
+		ep->hz, ep->pollival, ep->ntds, spname[ep->dev->speed],
+		spp, ep->maxpkt);
+	if(ep->maxpkt > 1024){
+		print("usb: %s: maxpkt %ld > 1024. truncating\n", s, ep->maxpkt);
+		ep->maxpkt = 1024;
+	}
+}
+
+/*
+ * Many endpoint ctls. simply update the portable representation
+ * of the endpoint. The actual controller driver will look
+ * at them to setup the endpoints as dictated.
+ */
+static long
+epctl(Ep *ep, Chan *c, void *a, long n)
+{
+	static char *Info = "info ";
+	Ep *nep;
+	Udev *d;
+	int l;
+	char *s;
+	char *b;
+	int tt;
+	int i;
+	int mode;
+	int nb;
+	Cmdtab *ct;
+	Cmdbuf *cb;
+
+	d = ep->dev;
+
+	cb = parsecmd(a, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	ct = lookupcmd(cb, epctls, nelem(epctls));
+	if(ct == nil)
+		error(Ebadctl);
+	i = ct->index;
+	if(i == CMnew || i == CMspeed || i == CMhub || i == CMpreset)
+		if(ep != ep->ep0)
+			error("allowed only on a setup endpoint");
+	if(i != CMclrhalt && i != CMdetach && i != CMdebugep && i != CMname)
+		if(ep != ep->ep0 && ep->inuse != 0)
+			error("must configure before using");
+	switch(i){
+	case CMnew:
+		deprint("usb epctl %s\n", cb->f[0]);
+		nb = strtol(cb->f[1], nil, 0);
+		if(nb < 0 || nb >= Ndeveps)
+			error("bad endpoint number");
+		tt = name2ttype(cb->f[2]);
+		if(tt == Tnone)
+			error("unknown endpoint type");
+		mode = name2mode(cb->f[3]);
+		if(mode < 0)
+			error("unknown i/o mode");
+		newdevep(ep, nb, tt, mode);
+		break;
+	case CMnewdev:
+		deprint("usb epctl %s\n", cb->f[0]);
+		if(ep != ep->ep0 || d->ishub == 0)
+			error("not a hub setup endpoint");
+		l = name2speed(cb->f[1]);
+		if(l == Nospeed)
+			error("speed must be full|low|high");
+		nep = newdev(ep->hp, 0, 0);
+		nep->dev->speed = l;
+		if(nep->dev->speed  != Lowspeed)
+			nep->maxpkt = 64;	/* assume full speed */
+		nep->dev->hub = d->nb;
+		nep->dev->port = atoi(cb->f[2]);
+		/* next read request will read
+		 * the name for the new endpoint
+		 */
+		l = sizeof(up->genbuf);
+		snprint(up->genbuf, l, "ep%d.%d", nep->dev->nb, nep->nb);
+		kstrdup(&c->aux, up->genbuf);
+		break;
+	case CMhub:
+		deprint("usb epctl %s\n", cb->f[0]);
+		d->ishub = 1;
+		break;
+	case CMspeed:
+		l = name2speed(cb->f[1]);
+		deprint("usb epctl %s %d\n", cb->f[0], l);
+		if(l == Nospeed)
+			error("speed must be full|low|high");
+		qlock(ep->ep0);
+		d->speed = l;
+		qunlock(ep->ep0);
+		break;
+	case CMmaxpkt:
+		l = strtoul(cb->f[1], nil, 0);
+		deprint("usb epctl %s %d\n", cb->f[0], l);
+		if(l < 1 || l > 1024)
+			error("maxpkt not in [1:1024]");
+		qlock(ep);
+		ep->maxpkt = l;
+		qunlock(ep);
+		break;
+	case CMntds:
+		l = strtoul(cb->f[1], nil, 0);
+		deprint("usb epctl %s %d\n", cb->f[0], l);
+		if(l < 1 || l > 3)
+			error("ntds not in [1:3]");
+		qlock(ep);
+		ep->ntds = l;
+		qunlock(ep);
+		break;
+	case CMpollival:
+		if(ep->ttype != Tintr && ep->ttype != Tiso)
+			error("not an intr or iso endpoint");
+		l = strtoul(cb->f[1], nil, 0);
+		deprint("usb epctl %s %d\n", cb->f[0], l);
+		if(ep->ttype == Tiso ||
+		   (ep->ttype == Tintr && ep->dev->speed == Highspeed)){
+			if(l < 1 || l > 16)
+				error("pollival power not in [1:16]");
+			l = pow2(l-1);
+		}else
+			if(l < 1 || l > 255)
+				error("pollival not in [1:255]");
+		qlock(ep);
+		ep->pollival = l;
+		if(ep->ttype == Tiso)
+			setmaxpkt(ep, "pollival");
+		qunlock(ep);
+		break;
+	case CMsamplesz:
+		if(ep->ttype != Tiso)
+			error("not an iso endpoint");
+		l = strtoul(cb->f[1], nil, 0);
+		deprint("usb epctl %s %d\n", cb->f[0], l);
+		if(l <= 0 || l > 8)
+			error("samplesz not in [1:8]");
+		qlock(ep);
+		ep->samplesz = l;
+		setmaxpkt(ep, "samplesz");
+		qunlock(ep);
+		break;
+	case CMhz:
+		if(ep->ttype != Tiso)
+			error("not an iso endpoint");
+		l = strtoul(cb->f[1], nil, 0);
+		deprint("usb epctl %s %d\n", cb->f[0], l);
+		if(l <= 0 || l > 100000)
+			error("hz not in [1:100000]");
+		qlock(ep);
+		ep->hz = l;
+		setmaxpkt(ep, "hz");
+		qunlock(ep);
+		break;
+	case CMclrhalt:
+		qlock(ep);
+		deprint("usb epctl %s\n", cb->f[0]);
+		ep->clrhalt = 1;
+		qunlock(ep);
+		break;
+	case CMinfo:
+		deprint("usb epctl %s\n", cb->f[0]);
+		l = strlen(Info);
+		s = a;
+		if(n < l+2 || strncmp(Info, s, l) != 0)
+			error(Ebadctl);
+		if(n > 1024)
+			n = 1024;
+		b = smalloc(n);
+		memmove(b, s+l, n-l);
+		b[n-l] = 0;
+		if(b[n-l-1] == '\n')
+			b[n-l-1] = 0;
+		qlock(ep);
+		free(ep->info);
+		ep->info = b;
+		qunlock(ep);
+		break;
+	case CMaddress:
+		deprint("usb epctl %s\n", cb->f[0]);
+		ep->dev->state = Denabled;
+		break;
+	case CMdetach:
+		if(ep->dev->isroot != 0)
+			error("can't detach a root hub");
+		deprint("usb epctl %s ep%d.%d\n",
+			cb->f[0], ep->dev->nb, ep->nb);
+		ep->dev->state = Ddetach;
+		/* Release file system ref. for its endpoints */
+		for(i = 0; i < nelem(ep->dev->eps); i++)
+			putep(ep->dev->eps[i]);
+		break;
+	case CMdebugep:
+		if(strcmp(cb->f[1], "on") == 0)
+			ep->debug = 1;
+		else if(strcmp(cb->f[1], "off") == 0)
+			ep->debug = 0;
+		else
+			ep->debug = strtoul(cb->f[1], nil, 0);
+		print("usb: ep%d.%d debug %d\n",
+			ep->dev->nb, ep->nb, ep->debug);
+		break;
+	case CMname:
+		deprint("usb epctl %s %s\n", cb->f[0], cb->f[1]);
+		validname(cb->f[1], 0);
+		kstrdup(&ep->name, cb->f[1]);
+		break;
+	case CMtmout:
+		deprint("usb epctl %s\n", cb->f[0]);
+		if(ep->ttype == Tiso || ep->ttype == Tctl)
+			error("ctl ignored for this endpoint type");
+		ep->tmout = strtoul(cb->f[1], nil, 0);
+		if(ep->tmout != 0 && ep->tmout < Xfertmout)
+			ep->tmout = Xfertmout;
+		break;
+	case CMpreset:
+		deprint("usb epctl %s\n", cb->f[0]);
+		if(ep->ttype != Tctl)
+			error("not a control endpoint");
+		if(ep->dev->state != Denabled)
+			error("forbidden on devices not enabled");
+		ep->dev->state = Dreset;
+		break;
+	default:
+		panic("usb: unknown epctl %d", ct->index);
+	}
+	free(cb);
+	poperror();
+	return n;
+}
+
+static long
+usbctl(void *a, long n)
+{
+	Cmdtab *ct;
+	Cmdbuf *cb;
+	Ep *ep;
+	int i;
+
+	cb = parsecmd(a, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	ct = lookupcmd(cb, usbctls, nelem(usbctls));
+	dprint("usb ctl %s\n", cb->f[0]);
+	switch(ct->index){
+	case CMdebug:
+		if(strcmp(cb->f[1], "on") == 0)
+			debug = 1;
+		else if(strcmp(cb->f[1], "off") == 0)
+			debug = 0;
+		else
+			debug = strtol(cb->f[1], nil, 0);
+		print("usb: debug %d\n", debug);
+		for(i = 0; i < epmax; i++)
+			if((ep = getep(i)) != nil){
+				ep->hp->debug(ep->hp, debug);
+				putep(ep);
+			}
+		break;
+	case CMdump:
+		dumpeps();
+		break;
+	}
+	free(cb);
+	poperror();
+	return n;
+}
+
+static long
+ctlwrite(Chan *c, void *a, long n)
+{
+	int q;
+	Ep *ep;
+
+	q = QID(c->qid);
+	if(q == Qctl)
+		return usbctl(a, n);
+
+	ep = getep(qid2epidx(q));
+	if(ep == nil)
+		error(Eio);
+	if(waserror()){
+		putep(ep);
+		nexterror();
+	}
+	if(ep->dev->state == Ddetach)
+		error(Edetach);
+	if(isqtype(q, Qepctl) && c->aux != nil){
+		/* Be sure we don't keep a cloned ep name */
+		free(c->aux);
+		c->aux = nil;
+		error("read, not write, expected");
+	}
+	n = epctl(ep, c, a, n);
+	putep(ep);
+	poperror();
+	return n;
+}
+
+static long
+usbwrite(Chan *c, void *a, long n, vlong off)
+{
+	int q;
+	Ep *ep;
+	int nr;
+
+	if(c->qid.type == QTDIR)
+		error(Eisdir);
+
+	q = QID(c->qid);
+
+	if(q == Qctl || isqtype(q, Qepctl))
+		return ctlwrite(c, a, n);
+
+	ep = getep(qid2epidx(q));
+	if(ep == nil)
+		error(Eio);
+	if(waserror()){
+		putep(ep);
+		nexterror();
+	}
+	if(ep->dev->state == Ddetach)
+		error(Edetach);
+	if(ep->mode == OREAD || ep->inuse == 0)
+		error(Ebadusefd);
+
+	switch(ep->ttype){
+	case Tnone:
+		error("endpoint not configured");
+	case Tctl:
+		nr = rhubwrite(ep, a, n);
+		if(nr >= 0){
+			n = nr;
+			break;
+		}
+		/* else fall */
+	default:
+		ddeprint("\nusbwrite q %#x fid %d cnt %ld off %lld\n",q, c->fid, n, off);
+		ep->hp->epwrite(ep, a, n);
+	}
+	putep(ep);
+	poperror();
+	return n;
+}
+
+void
+usbshutdown(void)
+{
+	Hci *hp;
+	int i;
+
+	for(i = 0; i < Nhcis; i++){
+		hp = hcis[i];
+		if(hp == nil)
+			continue;
+		if(hp->shutdown == nil)
+			print("#u: no shutdown function for %s\n", hp->type);
+		else
+			hp->shutdown(hp);
+	}
+}
+
+Dev usbdevtab = {
+	L'u',
+	"usb",
+
+	usbreset,
+	usbinit,
+	usbshutdown,
+	usbattach,
+	usbwalk,
+	usbstat,
+	usbopen,
+	devcreate,
+	usbclose,
+	usbread,
+	devbread,
+	usbwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};

+ 263 - 0
sys/src/9/omap/dma.c

@@ -0,0 +1,263 @@
+/*
+ * omap3530 system dma controller
+ *
+ * terminology: a block consist of frame(s), a frame consist of elements
+ * (uchar, ushort, or ulong sized).
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+enum {
+	Nirq	= 4,
+	Baseirq	= 12,
+
+	Nchan	= 32,
+};
+
+/*
+ * has a sw reset bit
+ * dma req lines 1, 2, 6, 63 are available for `system expansion'
+ */
+
+typedef struct Regs Regs;
+typedef struct Dchan Dchan;
+struct Regs {
+	uchar	_pad0[8];
+	/* bitfield of intrs pending, by Dchan; write 1s to clear */
+	ulong	irqsts[Nirq];
+	ulong	irqen[Nirq];	/* bitfield of intrs enabled, by Dchan */
+	ulong	syssts;		/* 1<<0 is Resetdone */
+	ulong	syscfg;		/* 1<<1 is Softreset */
+	uchar	_pad1[0x64 - 0x30];
+
+	ulong	caps[5];	/* caps[1] not defined */
+	ulong	gcr;		/* knobs */
+	ulong	_pad2;
+
+	struct Dchan {
+		ulong	ccr;	/* chan ctrl: incr, etc. */
+		ulong	clnkctrl; /* link ctrl */
+		ulong	cicr;	/* intr ctrl */
+		ulong	csr;	/* status */
+		ulong	csdp;	/* src & dest params */
+		ulong	cen;	/* element # */
+		ulong	cfn;	/* frame # */
+		ulong	cssa;	/* src start addr */
+		ulong	cdsa;	/* dest start addr */
+		ulong	csei;	/* src element index */
+		ulong	csfi;	/* src frame index | pkt size */
+		ulong	cdei;	/* dest element index */
+		ulong	cdfi;	/* dest frame index | pkt size */
+		ulong	csac;	/* src addr value (read-only?) */
+		ulong	cdac;	/* dest addr value */
+		ulong	ccen;	/* curr transferred element # (in frame) */
+		ulong	ccfn;	/* curr transferred frame # (in xfer) */
+		ulong	color;
+		uchar	_pad3[24];
+	} chan[Nchan];
+};
+
+enum {
+	/* cicr/csr bits */
+	Blocki	= 1 << 5,
+
+	/* ccr bits */
+	Enable	= 1 << 7,
+};
+
+typedef struct Xfer Xfer;
+static struct Xfer {
+	Rendez	*rend;
+	int	*done;		/* flag to set on intr */
+} xfer[Nirq];
+
+int
+isdmadone(int irq)
+{
+	Dchan *cp;
+	Regs *regs = (Regs *)PHYSSDMA;
+
+	cp = regs->chan + irq;
+	return cp->csr & Blocki;
+}
+
+static void
+dmaintr(Ureg *, void *a)
+{
+	int i = (int)a;			/* dma request & chan # */
+	Dchan *cp;
+	Regs *regs = (Regs *)PHYSSDMA;
+
+	assert(i >= 0 && i < Nirq);
+
+	*xfer[i].done = 1;
+	assert(xfer[i].rend != nil);
+	wakeup(xfer[i].rend);
+
+	cp = regs->chan + i;
+	if(!(cp->csr & Blocki))
+		iprint("dmaintr: req %d: Blocki not set; csr %#lux\n",
+			i, cp->csr);
+	cp->csr |= cp->csr;			/* extinguish intr source */
+	coherence();
+	regs->irqsts[i] = regs->irqsts[i];	/* extinguish intr source */
+	coherence();
+	regs->irqen[i] &= ~(1 << i);
+	coherence();
+
+	xfer[i].rend = nil;
+	coherence();
+}
+
+void
+zerowds(ulong *wdp, int cnt)
+{
+	while (cnt-- > 0)
+		*wdp++ = 0;
+}
+
+static int
+istestdmadone(void *arg)
+{
+	return *(int *)arg;
+}
+
+void
+dmainit(void)
+{
+	int n;
+	char name[16];
+	Dchan *cp;
+	Regs *regs = (Regs *)PHYSSDMA;
+
+	if (probeaddr((uintptr)&regs->syssts) < 0)
+		panic("dmainit: no syssts reg");
+	regs->syssts = 0;
+	coherence();
+	regs->syscfg |= 1<<1;		/* Softreset */
+	coherence();
+	while(!(regs->syssts & (1<<0)))	/* Resetdone? */
+		;
+
+	for (n = 0; n < Nchan; n++) {
+		cp = regs->chan + n;
+		cp->ccr = 0;
+		cp->clnkctrl = 0;
+		cp->cicr = 0;
+		cp->csr = 0;
+		cp->csdp = 0;
+		cp->cen = cp->cfn = 0;
+		cp->cssa = cp->cdsa = 0;
+		cp->csei = cp->csfi = 0;
+		cp->cdei = cp->cdfi = 0;
+//		cp->csac = cp->cdac = 0;		// ro
+		cp->ccen = cp->ccfn = 0;
+		cp->color = 0;
+	}
+	zerowds((void *)regs->irqsts, sizeof regs->irqsts / sizeof(ulong));
+	zerowds((void *)regs->irqen,  sizeof regs->irqen / sizeof(ulong));
+	coherence();
+
+	regs->gcr = 65;			/* burst size + 1 */
+	coherence();
+
+	for (n = 0; n < Nirq; n++) {
+		snprint(name, sizeof name, "dma%d", n);
+		intrenable(Baseirq + n, dmaintr, (void *)n, nil, name);
+	}
+}
+
+enum {
+	Testbyte	= 0252,
+	Testsize	= 256,
+	Scratch		= MB,
+};
+
+/*
+ * try to confirm sane operation
+ */
+void
+dmatest(void)
+{
+	int n, done;
+	uchar *bp;
+	static ulong pat = 0x87654321;
+	static Rendez trendez;
+
+	if (up == nil)
+		panic("dmatest: up not set yet");
+	bp = (uchar *)KADDR(PHYSDRAM + 128*MB);
+	memset(bp, Testbyte, Scratch);
+	done = 0;
+	dmastart((void *)PADDR(bp), Postincr, (void *)PADDR(&pat), Const,
+		Testsize, &trendez, &done);
+	sleep(&trendez, istestdmadone, &done);
+	cachedinvse(bp, Scratch);
+
+	if (((ulong *)bp)[0] != pat)
+		panic("dmainit: copied incorrect data %#lux != %#lux",
+			((ulong *)bp)[0], pat);
+	for (n = Testsize; n < Scratch && bp[n] != Testbyte; n++)
+		;
+	if (n >= Scratch)
+		panic("dmainit: ran wild over memory, clobbered ≥%,d bytes", n);
+	if (bp[n] == Testbyte && n != Testsize)
+		iprint("dma: %d-byte dma stopped after %d bytes!\n",
+			Testsize, n);
+}
+
+/* addresses are physical */
+int
+dmastart(void *to, int tmode, void *from, int fmode, uint len, Rendez *rend,
+	int *done)
+{
+	int irq, chan;
+	uint ruplen;
+	Dchan *cp;
+	Regs *regs = (Regs *)PHYSSDMA;
+	static Lock alloclck;
+
+	/* allocate free irq (and chan) */
+	ilock(&alloclck);
+	for (irq = 0; irq < Nirq && xfer[irq].rend != nil; irq++)
+		;
+	if (irq >= Nirq)
+		panic("dmastart: no available irqs; too many concurrent dmas");
+	chan = irq;
+	xfer[irq].rend = rend;			/* for wakeup at intr time */
+	xfer[irq].done = done;
+	*done = 0;
+	iunlock(&alloclck);
+
+	ruplen = ROUNDUP(len, sizeof(ulong));
+	assert(to != from);
+
+	cp = regs->chan + chan;
+	cp->ccr &= ~Enable;			/* paranoia */
+	cp->cicr = 0;
+	regs->irqen[irq] &= ~(1 << chan);
+	coherence();
+
+	cp->csdp = 2;				/* 2 = log2(sizeof(ulong)) */
+	cp->cssa = (uintptr)from;
+	cp->cdsa = (uintptr)to;
+	cp->ccr = tmode << 14 | fmode << 12;
+	cp->csei = cp->csfi = cp->cdei = cp->cdfi = 1;
+	cp->cen = ruplen / sizeof(ulong);	/* ulongs / frame */
+	cp->cfn = 1;				/* 1 frame / xfer */
+	cp->cicr = Blocki;			/* intr at end of block */
+
+	regs->irqen[irq] |= 1 << chan;
+	coherence();
+
+	cp->ccr |= Enable;			/* fire! */
+	coherence();
+
+	return irq;
+}

+ 963 - 0
sys/src/9/omap/ether9221.c

@@ -0,0 +1,963 @@
+/*
+ * SMSC 9221 Ethernet driver
+ * specifically for the ISEE IGEPv2 board,
+ * where it is assigned to Chip Select 5,
+ * its registers are at 0x2c000000 (inherited from u-boot),
+ * and irq is 34 from gpio pin 176, thus gpio module 6.
+ *
+ * it's slow due to the use of fifos instead of buffer rings.
+ * the slow system dma just makes it worse.
+ *
+ * igepv2 u-boot uses pin 64 on gpio 3 as an output pin to reset the 9221.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+
+/* currently using kprocs is a lot slower than not (87 s. to boot vs 60) */
+#undef USE_KPROCS
+
+enum {
+	Vid9221	= 0x9221,
+	Slop	= 4,			/* beyond ETHERMAXTU */
+};
+
+typedef struct Regs Regs;
+struct Regs {
+	/* fifo ports */
+	ulong	rxdata;
+	uchar	_pad0[0x20 - 4];
+	ulong	txdata;
+	uchar	_pad1[0x40 - 0x24];
+	ulong	rxsts;
+	ulong	rxstspeek;
+	ulong	txsts;
+	ulong	txstspeek;
+
+	/* control & status */
+	ushort	rev;			/* chip revision */
+	ushort	id;			/* chip id, 0x9221 */
+	ulong	irqcfg;
+	ulong	intsts;
+	ulong	inten;
+	ulong	_pad2;
+	ulong	bytetest;
+	ulong	fifoint;		/* fifo level interrupts */
+	ulong	rxcfg;
+	ulong	txcfg;
+	ulong	hwcfg;
+	ulong	rxdpctl;		/* rx data path control */
+	ulong	rxfifoinf;
+	ulong	txfifoinf;
+	ulong	pmtctl;			/* power mgmt. control */
+	ulong	gpiocfg;
+	ulong	gptcfg;			/* timer */
+	ulong	gptcnt;
+	ulong	_pad3;
+	ulong	wordswap;
+	ulong	freerun; 		/* counters */
+	ulong	rxdrop;
+
+	/*
+	 * mac registers are accessed indirectly via the mac csr registers.
+	 * phy registers are doubly indirect, via the mac csr mii_acc &
+	 * mii_data mac csr registers.
+	 */
+	ulong	maccsrcmd;		/* mac csr synchronizer */
+	ulong	maccsrdata;
+	ulong	afccfg;			/* automatic flow control cfg. */
+	ulong	eepcmd;			/* eeprom */
+	ulong	eepdata;
+	/* 0xb8 */
+};
+
+enum {
+	Nstatistics	= 128,
+};
+
+enum {
+	/* txcmda bits */
+	Intcompl	= 1<<31,
+	Bufendalign	= 3<<24,	/* mask */
+	Datastoff	= 037<<16,	/* mask */
+	Firstseg	= 1<<13,
+	Lastseg		= 1<<12,
+	Bufsize		= MASK(11),
+
+	/* txcmdb bits */
+	Pkttag		= MASK(16) << 16,
+	Txcksumen	= 1<<14,
+	Addcrcdis	= 1<<13,
+	Framepaddis	= 1<<12,
+	Pktlen		= (1<<1) - 1,	/* mask */
+
+	/* txcfg bits */
+	Txsdump		= 1<<15,	/* flush tx status fifo */
+	Txddump		= 1<<14,	/* flush tx data fifo */
+	Txon		= 1<<1,
+	Stoptx		= 1<<0,
+
+	/* hwcfg bits */
+	Mbo		= 1<<20,	/* must be one */
+	Srstto		= 1<<1,		/* soft reset time-out */
+	Srst		= 1<<0,
+
+	/* rxcfg bits */
+	Rxdmacntshift	= 16,		/* ulong count, 12 bits wide */
+	Rxdmacntmask	= MASK(12) << Rxdmacntshift,
+	Rxdump		= 1<<15,	/* flush rx fifos */
+
+	/* rxsts bits */
+	Rxpktlenshift	= 16,		/* byte count */
+	Rxpktlenmask	= MASK(14) << Rxpktlenshift,
+	Rxerr		= 1<<15,
+
+	/* rxfifoinf bits */
+	Rxstsusedshift	= 16,		/* ulong count */
+	Rxstsusedmask	= MASK(8) << Rxstsusedshift,
+	Rxdatausedmask	= MASK(16),	/* byte count */
+
+	/* txfifoinf bits */
+	Txstsusedshift	= 16,		/* ulong count */
+	Txstsusedmask	= MASK(8) << Txstsusedshift,
+	Txdatafreemask	= MASK(16),	/* byte count */
+
+	/* pmtctl bits */
+	Dready		= 1<<0,
+
+	/* maccsrcmd bits */
+	Csrbusy		= 1<<31,
+	Csrread		= 1<<30,	/* not write */
+	Csraddrshift	= 0,
+	Csraddrmask	= MASK(8) - 1,
+
+	/* mac registers' indices */
+	Maccr		= 1,
+	Macaddrh,
+	Macaddrl,
+	Machashh,
+	Machashl,
+	Macmiiacc,			/* for doubly-indirect phy access */
+	Macmiidata,
+	Macflow,
+	Macvlan1,
+	Macvlan2,
+	Macwuff,
+	Macwucsr,
+	Maccoe,
+
+	/* Maccr bits */
+	Rxall		= 1<<31,
+	Rcvown		= 1<<23,	/* don't receive own transmissions */
+	Fdpx		= 1<<20,	/* full duplex */
+	Mcpas		= 1<<19,	/* pass all multicast */
+	Prms		= 1<<18,	/* promiscuous */
+	Ho		= 1<<15,	/* hash-only filtering */
+	Hpfilt		= 1<<13,	/* hash/perfect filtering */
+	Padstr		= 1<<8,		/* strip padding & fcs (crc) */
+	Txen		= 1<<3,
+	Rxen		= 1<<2,
+
+	/* irqcfg bits */
+	Irqdeasclr	= 1<<14,	/* deassertion intv'l clear */
+	Irqdeassts	= 1<<13,	/* deassertion intv'l status */
+	Irqint		= 1<<12,	/* intr being asserted? (ro) */
+	Irqen		= 1<<8,
+	Irqpol		= 1<<4,		/* irq output is active high */
+	Irqpushpull	= 1<<0,		/* irq output is push/pull driver */
+
+	/* intsts/inten bits */
+	Swint		= 1<<31,	/* generate an interrupt */
+	Txstop		= 1<<25,
+	Rxstop		= 1<<24,
+	Txioc		= 1<<21,
+	Rxdma		= 1<<20,
+	Gptimer		= 1<<19,
+	Phy		= 1<<18,
+	Rxe		= 1<<14,	/* errors */
+	Txe		= 1<<13,
+	Tdfo		= 1<<10,	/* tx data fifo overrun */
+	Tdfa		= 1<<9,		/* tx data fifo available */
+	Tsff		= 1<<8,		/* tx status fifo full */
+	Tsfl		= 1<<7,		/* tx status fifo level */
+	Rsff		= 1<<4,		/* rx status fifo full */
+	Rsfl		= 1<<3,		/* rx status fifo level */
+
+	/* eepcmd bits */
+	Epcbusy		= 1<<31,
+	Epccmdshift	= 28,		/* interesting one is Reload (7) */
+	Epctimeout	= 1<<9,
+	Epcmacloaded	= 1<<8,
+	Epcaddrshift	= 0,
+};
+
+enum {
+	Rxintrs		= Rsff | Rsfl | Rxe,
+	Txintrs		= Tsff | Tsfl | Txe | Txioc,
+};
+
+/* wake-up frame filter */
+struct Wakeup {
+	ulong	bytemask[4];		/* index is filter # */
+	uchar	filt0cmd;		/* filter 0 command */
+	uchar	_pad0;
+	uchar	filt1cmd;
+	uchar	_pad1;
+	uchar	filt2cmd;
+	uchar	_pad2;
+	uchar	filt3cmd;
+	uchar	_pad3;
+	uchar	offset[4];		/* index is filter # */
+	ushort	crc16[4];		/* " */
+};
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	int	port;
+	Ctlr*	next;
+	Ether*	edev;
+	Regs*	regs;
+	int	active;
+	int	started;
+	int	inited;
+	int	id;
+	int	cls;
+	ushort	eeprom[0x40];
+
+	QLock	alock;			/* attach */
+	int	nrb;			/* how many this Ctlr has in the pool */
+
+	int*	nic;
+	Lock	imlock;
+	int	im;			/* interrupt mask */
+
+//	Mii*	mii;
+//	Rendez	lrendez;
+	int	lim;
+
+	int	link;
+
+	QLock	slock;
+	uint	statistics[Nstatistics];
+	uint	lsleep;
+	uint	lintr;
+	uint	rsleep;
+	uint	rintr;
+	int	tsleep;
+	uint	tintr;
+
+	uchar	ra[Eaddrlen];		/* receive address */
+	ulong	mta[128];		/* multicast table array */
+
+	Rendez	rrendez;
+	int	gotinput;
+	int	rdcpydone;
+
+	Rendez	trendez;
+	int	gotoutput;
+	int	wrcpydone;
+
+	Lock	tlock;
+};
+
+#define csr32r(c, r)	(*((c)->nic+((r)/4)))
+#define csr32w(c, r, v)	(*((c)->nic+((r)/4)) = (v))
+
+static Ctlr *smcctlrhead, *smcctlrtail;
+
+static char* statistics[Nstatistics] = { "dummy", };
+
+static uchar mymac[] = { 0xb0, 0x0f, 0xba, 0xbe, 0x00, 0x00, };
+
+static void etherclock(void);
+static void smcreceive(Ether *edev);
+static void smcinterrupt(Ureg*, void* arg);
+
+static Ether *thisether;
+static int attached;
+
+static void
+smconce(Ether *edev)
+{
+	static int beenhere;
+	static Lock l;
+
+	ilock(&l);
+	if (!beenhere && edev != nil) {
+		beenhere = 1;
+		/* simulate interrupts if we don't know the irq */
+		if (edev->irq < 0) {		/* poll as backup */
+			thisether = edev;
+			addclock0link(etherclock, 1000/HZ);
+			iprint(" polling");
+		}
+	}
+	iunlock(&l);
+}
+
+/*
+ * indirect (mac) register access
+ */
+
+static void
+macwait(Regs *regs)
+{
+	long bound;
+
+	for (bound = 400*1000*1000; regs->maccsrcmd & Csrbusy && bound > 0;
+	    bound--)
+		;
+	if (bound <= 0)
+		iprint("smc: mac registers didn't come ready\n");
+}
+
+static ulong
+macrd(Regs *regs, uchar index)
+{
+	macwait(regs);
+	regs->maccsrcmd = Csrbusy | Csrread | index;
+	coherence();		/* back-to-back write/read delay per §6.2.1 */
+	macwait(regs);
+	return regs->maccsrdata;
+}
+
+static void
+macwr(Regs *regs, uchar index, ulong val)
+{
+	macwait(regs);
+	regs->maccsrdata = val;
+	regs->maccsrcmd = Csrbusy | index;	/* fire */
+	macwait(regs);
+}
+
+
+static long
+smcifstat(Ether* edev, void* a, long n, ulong offset)
+{
+	Ctlr *ctlr;
+	char *p, *s;
+	int i, l, r;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->slock);
+	p = malloc(READSTR);
+	l = 0;
+	for(i = 0; i < Nstatistics; i++){
+		// read regs->rxdrop TODO
+		r = 0;
+		if((s = statistics[i]) == nil)
+			continue;
+		switch(i){
+		default:
+			ctlr->statistics[i] += r;
+			if(ctlr->statistics[i] == 0)
+				continue;
+			l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
+				s, ctlr->statistics[i], r);
+			break;
+		}
+	}
+
+	l += snprint(p+l, READSTR-l, "lintr: %ud %ud\n",
+		ctlr->lintr, ctlr->lsleep);
+	l += snprint(p+l, READSTR-l, "rintr: %ud %ud\n",
+		ctlr->rintr, ctlr->rsleep);
+	l += snprint(p+l, READSTR-l, "tintr: %ud %ud\n",
+		ctlr->tintr, ctlr->tsleep);
+
+	l += snprint(p+l, READSTR-l, "eeprom:");
+	for(i = 0; i < 0x40; i++){
+		if(i && ((i & 0x07) == 0))
+			l += snprint(p+l, READSTR-l, "\n       ");
+		l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->eeprom[i]);
+	}
+	l += snprint(p+l, READSTR-l, "\n");
+	USED(l);
+
+	n = readstr(offset, a, n, p);
+	free(p);
+	qunlock(&ctlr->slock);
+
+	return n;
+}
+
+static void
+smcpromiscuous(void* arg, int on)
+{
+	int rctl;
+	Ctlr *ctlr;
+	Ether *edev;
+	Regs *regs;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	regs = ctlr->regs;
+	rctl = macrd(regs, Maccr);
+	if(on)
+		rctl |= Prms;
+	else
+		rctl &= ~Prms;
+	macwr(regs, Maccr, rctl);
+}
+
+static void
+smcmulticast(void*, uchar*, int)
+{
+	/* nothing to do, we allow all multicast packets in */
+}
+
+static int
+iswrcpydone(void *arg)
+{
+	return ((Ctlr *)arg)->wrcpydone;
+}
+
+static int
+smctxstart(Ctlr *ctlr, uchar *ubuf, uint len)
+{
+	uint wds, ruplen;
+	ulong *wdp, *txdp;
+	Regs *regs;
+	static ulong buf[ROUNDUP(ETHERMAXTU, sizeof(ulong)) / sizeof(ulong)];
+
+	if (!ctlr->inited) {
+		iprint("smctxstart: too soon to send\n");
+		return -1;		/* toss it */
+	}
+	regs = ctlr->regs;
+
+	/* is there room for a packet in the tx data fifo? */
+	if (len < ETHERMINTU)
+		iprint("sending too-short (%d) pkt\n", len);
+	else if (len > ETHERMAXTU)
+		iprint("sending jumbo (%d) pkt\n", len);
+
+	ruplen = ROUNDUP(len, sizeof(ulong));
+	coherence();	/* back-to-back read/read delay per §6.2.2 */
+	if ((regs->txfifoinf & Txdatafreemask) < ruplen + 2*sizeof(ulong))
+		return -1;	/* not enough room for data + command words */
+
+	if ((uintptr)ubuf & MASK(2)) {		/* ensure word alignment */
+		memmove(buf, ubuf, len);
+		ubuf = (uchar *)buf;
+	}
+
+	/* tx cmd a: length is bytes in this buffer */
+	txdp = &regs->txdata;
+	*txdp = Intcompl | Firstseg | Lastseg | len;
+	/* tx cmd b: length is bytes in this packet (could be multiple buf.s) */
+	*txdp = len;
+
+	/* shovel pkt into tx fifo, which triggers transmission due to Txon */
+	wdp = (ulong *)ubuf;
+	for (wds = ruplen / sizeof(ulong) + 1; --wds > 0; )
+		*txdp = *wdp++;
+
+	regs->intsts = Txintrs;		/* dismiss intr */
+	coherence();
+	regs->inten |= Txintrs;
+	coherence();		/* back-to-back write/read delay per §6.2.1 */
+	return 0;
+}
+
+static void
+smctransmit(Ether* edev)
+{
+	Block *bp;
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	if (ctlr == nil)
+		panic("smctransmit: nil ctlr");
+	ilock(&ctlr->tlock);
+	/*
+	 * Try to fill the chip's buffers back up, via the tx fifo.
+	 */
+	while ((bp = qget(edev->oq)) != nil)
+		if (smctxstart(ctlr, bp->rp, BLEN(bp)) < 0) {
+			qputback(edev->oq, bp);	/* retry the block later */
+			iprint("smctransmit: tx data fifo full\n");
+			break;
+		} else
+			freeb(bp);
+	iunlock(&ctlr->tlock);
+}
+
+static void
+smctransmitcall(Ether *edev)		/* called from devether.c */
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	ctlr->gotoutput = 1;
+#ifdef USE_KPROCS
+	wakeup(&ctlr->trendez);
+#else
+	smctransmit(edev);
+#endif
+}
+
+static int
+smcrim(void* ctlr)
+{
+	return ((Ctlr*)ctlr)->gotinput;
+}
+
+static void
+smcrproc(void* arg)
+{
+	Ctlr *ctlr;
+	Ether *edev;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	for(;;){
+		ctlr->rsleep++;
+		sleep(&ctlr->rrendez, smcrim, ctlr);
+
+		/* process any newly-arrived packets and pass to etheriq */
+		ctlr->gotinput = 0;
+		smcreceive(edev);
+	}
+}
+
+static int
+smcgotout(void* ctlr)
+{
+	return ((Ctlr*)ctlr)->gotoutput;
+}
+
+static void
+smctproc(void* arg)
+{
+	Ctlr *ctlr;
+	Ether *edev;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	for(;;){
+		ctlr->tsleep++;
+		sleep(&ctlr->trendez, smcgotout, ctlr);
+
+		/* process any newly-arrived packets and pass to etheriq */
+		ctlr->gotoutput = 0;
+		smctransmit(edev);
+	}
+}
+
+void	gpioirqclr(void);
+
+static void
+smcattach(Ether* edev)
+{
+#ifdef USE_KPROCS
+	char name[KNAMELEN];
+#endif
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->alock);
+	if(waserror()){
+		qunlock(&ctlr->alock);
+		nexterror();
+	}
+	if (!ctlr->inited) {
+		ctlr->inited = 1;
+#ifdef USE_KPROCS
+		snprint(name, KNAMELEN, "#l%drproc", edev->ctlrno);
+		kproc(name, smcrproc, edev);
+
+		snprint(name, KNAMELEN, "#l%dtproc", edev->ctlrno);
+		kproc(name, smctproc, edev);
+#endif
+
+iprint("smcattach:");
+#ifdef USE_KPROCS
+iprint(" with kprocs");
+#else
+iprint(" no kprocs");
+#endif
+iprint(", no dma");
+		/* can now accept real or simulated interrupts */
+
+		smconce(edev);
+		attached = 1;
+iprint("\n");
+	}
+	qunlock(&ctlr->alock);
+	poperror();
+}
+
+static int
+isrdcpydone(void *arg)
+{
+	return ((Ctlr *)arg)->rdcpydone;
+}
+
+static void
+smcreceive(Ether *edev)
+{
+	uint wds, len, sts;
+	ulong *wdp, *rxdp;
+	Block *bp;
+	Ctlr *ctlr;
+	Regs *regs;
+
+	ctlr = edev->ctlr;
+	regs = ctlr->regs;
+	coherence();		/* back-to-back read/read delay per §6.2.2 */
+	/*
+	 * is there a full packet in the rx data fifo?
+	 */
+	while (((regs->rxfifoinf & Rxstsusedmask) >> Rxstsusedshift) != 0) {
+		coherence();
+		sts = regs->rxsts;		/* pop rx status */
+		if(sts & Rxerr)
+			iprint("smcreceive: rx error\n");
+		len = (sts & Rxpktlenmask) >> Rxpktlenshift;
+		if (len > ETHERMAXTU + Slop)
+			iprint("smcreceive: oversized rx pkt (%d)\n", len);
+		else if (len < ETHERMINTU)
+			iprint("smcreceive: too-short (%d) pkt\n", len);
+		wds = ROUNDUP(len, sizeof(ulong)) / sizeof(ulong);
+		if (wds > 0) {
+			/* copy aligned words from rx fifo into a Block */
+			bp = iallocb(len + sizeof(ulong) /* - 1 */);
+			if (bp == nil)
+				panic("smcreceive: nil Block*");
+
+			/* bp->rp should be 32-byte aligned, more than we need */
+			assert(((uintptr)bp->rp & (sizeof(ulong) - 1)) == 0);
+			wdp = (ulong *)bp->rp;
+			rxdp = &regs->rxdata;
+			wds = ROUNDUP(len, sizeof(ulong)) / sizeof(ulong) + 1;
+			while (--wds > 0)
+				*wdp++ = *rxdp;
+			bp->wp = bp->rp + len;
+
+			/* and push the Block upstream */
+			if (ctlr->inited)
+				etheriq(edev, bp, 1);
+			else
+				freeb(bp);
+
+			regs->intsts = Rxintrs;		/* dismiss intr */
+			coherence();
+			regs->inten |= Rxintrs;
+		}
+		coherence();
+	}
+	regs->inten |= Rxintrs;
+	coherence();
+}
+
+/*
+ * disable the stsclr bits in inten and write them to intsts to ack and dismiss
+ * the interrupt source.
+ */
+void
+ackintr(Regs *regs, ulong stsclr)
+{
+	if (stsclr == 0)
+		return;
+
+	regs->inten &= ~stsclr;
+	coherence();
+
+//	regs->intsts = stsclr;		/* acknowledge & clear intr(s) */
+//	coherence();
+}
+
+static void
+smcinterrupt(Ureg*, void* arg)
+{
+	int junk;
+	unsigned intsts, intr;
+	Ctlr *ctlr;
+	Ether *edev;
+	Regs *regs;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	ilock(&ctlr->imlock);
+	regs = ctlr->regs;
+
+	gpioirqclr();
+
+	coherence();		/* back-to-back read/read delay per §6.2.2 */
+	intsts = regs->intsts;
+	coherence();
+
+	intsts &= ~MASK(3);		/* ignore gpio bits */
+	if (0 && intsts == 0) {
+		coherence();
+		iprint("smc: interrupt without a cause; insts %#ux (vs inten %#lux)\n",
+			intsts, regs->inten);
+	}
+
+	intr = intsts & Rxintrs;
+	if(intr) {
+		/* disable interrupt sources; kproc/smcreceive will reenable */
+		ackintr(regs, intr);
+
+		ctlr->rintr++;
+		ctlr->gotinput = 1;
+#ifdef USE_KPROCS
+		wakeup(&ctlr->rrendez);
+#else
+		smcreceive(edev);
+#endif
+	}
+
+	while(((regs->txfifoinf & Txstsusedmask) >> Txstsusedshift) != 0) {
+		/* probably indicates tx completion, just toss it */
+		junk = regs->txsts;		/* pop tx sts */
+		USED(junk);
+		coherence();
+	}
+
+	intr = intsts & Txintrs;
+	if (ctlr->gotoutput || intr) {
+		/* disable interrupt sources; kproc/smctransmit will reenable */
+		ackintr(regs, intr);
+
+		ctlr->tintr++;
+		ctlr->gotoutput = 1;
+#ifdef USE_KPROCS
+		wakeup(&ctlr->trendez);
+#else
+		smctransmit(edev);
+#endif
+	}
+
+	iunlock(&ctlr->imlock);
+}
+
+static void
+etherclock(void)
+{
+	smcinterrupt(nil, thisether);
+}
+
+static int
+smcmii(Ctlr *)
+{
+	return 0;
+}
+
+static int
+smcdetach(Ctlr* ctlr)
+{
+	Regs *regs;
+
+	if (ctlr == nil || ctlr->regs == nil)
+		return -1;
+	regs = ctlr->regs;
+	/* verify that it's real by reading a few registers */
+	switch (regs->id) {
+	case Vid9221:
+		break;
+	default:
+		print("smc: unknown chip id %#ux\n", regs->id);
+		return -1;
+	}
+	regs->inten = 0;		/* no interrupts */
+	regs->intsts = ~0;		/* clear any pending */
+	regs->gptcfg = 0;
+	coherence();
+	regs->rxcfg = Rxdump;
+	regs->txcfg = Txsdump | Txddump;
+	regs->irqcfg &= ~Irqen;
+	coherence();
+	return 0;
+}
+
+static void
+smcshutdown(Ether* ether)
+{
+	smcdetach(ether->ctlr);
+}
+
+static void
+powerwait(Regs *regs)
+{
+	long bound;
+
+	regs->bytetest = 0;			/* bring power on */
+	for (bound = 400*1000*1000; !(regs->pmtctl & Dready) && bound > 0;
+	    bound--)
+		;
+	if (bound <= 0)
+		iprint("smc: pmtctl didn't come ready\n");
+}
+
+static int
+smcreset(Ctlr* ctlr)
+{
+	int r;
+	Regs *regs;
+	static char zea[Eaddrlen];
+
+	regs = ctlr->regs;
+	powerwait(regs);
+
+	if(smcdetach(ctlr))
+		return -1;
+
+	/* verify that it's real by reading a few registers */
+	switch (regs->id) {
+	case Vid9221:
+		break;
+	default:
+		print("smc: unknown chip id %#ux\n", regs->id);
+		return -1;
+	}
+	if (regs->bytetest != 0x87654321) {
+		print("smc: bytetest reg %#p (%#lux) != 0x87654321\n",
+			&regs->bytetest, regs->bytetest);
+		return -1;
+	}
+
+#ifdef TODO			/* read MAC from EEPROM */
+//	int ctrl, i, pause, swdpio, txcw;
+	/*
+	 * Snarf and set up the receive addresses.
+	 * There are 16 addresses. The first should be the MAC address.
+	 * The others are cleared and not marked valid (MS bit of Rah).
+	 */
+	for(i = Ea; i < Eaddrlen/2; i++){
+		ctlr->ra[2*i] = ctlr->eeprom[i];
+		ctlr->ra[2*i+1] = ctlr->eeprom[i]>>8;
+	}
+
+	/*
+	 * Clear the Multicast Table Array.
+	 * It's a 4096 bit vector accessed as 128 32-bit registers.
+	 */
+	memset(ctlr->mta, 0, sizeof(ctlr->mta));
+	for(i = 0; i < 128; i++)
+		csr32w(ctlr, Mta+i*4, 0);
+#endif
+	regs->hwcfg |= Mbo;
+
+	/* don't overwrite existing ea */
+//	if (memcmp(edev->ea, zea, Eaddrlen) == 0)
+//		memmove(edev->ea, ctlr->ra, Eaddrlen);
+
+	r = ctlr->ra[3]<<24 | ctlr->ra[2]<<16 | ctlr->ra[1]<<8 | ctlr->ra[0];
+	macwr(regs, Macaddrl, r);
+	macwr(regs, Macaddrh, ctlr->ra[5]<<8 | ctlr->ra[4]);
+
+	/* turn on the controller */
+	macwr(regs, Maccoe, 0);
+	regs->inten = 0;		/* no interrupts yet */
+	regs->intsts = ~0;		/* clear any pending */
+	regs->gptcfg = 0;
+	coherence();
+	regs->rxcfg = Rxdump;
+	regs->txcfg = Txsdump | Txddump | Txon;
+	regs->fifoint = 72<<24;		/* default values */
+	macwr(regs, Maccr, Rxall | Rcvown | Fdpx | Mcpas | Txen | Rxen);
+	coherence();		/* back-to-back write/read delay per §6.2.1 */
+	regs->irqcfg = 1<<24 | Irqen | Irqpushpull;  /* deas for 10µs (linux) */
+	coherence();		/* back-to-back write/read delay per §6.2.1 */
+	regs->inten = Rxintrs | Txintrs;
+	coherence();
+
+	if(smcmii(ctlr) < 0)
+		return -1;
+	return 0;
+}
+
+static void
+smcpci(void)
+{
+	Ctlr *ctlr;
+	static int beenhere;
+
+	if (beenhere)
+		return;
+	beenhere = 1;
+
+	if (probeaddr(PHYSETHER) < 0)
+		return;
+	ctlr = malloc(sizeof(Ctlr));
+	ctlr->id = Vid9221<<16 | 0x0424;	/* smsc 9221 */
+	ctlr->port = PHYSETHER;
+	ctlr->nic = (int *)PHYSETHER;
+	ctlr->regs = (Regs *)PHYSETHER;
+
+	if(smcreset(ctlr)){
+		free(ctlr);
+		return;
+	}
+	if(smcctlrhead != nil)
+		smcctlrtail->next = ctlr;
+	else
+		smcctlrhead = ctlr;
+	smcctlrtail = ctlr;
+}
+
+static int
+smcpnp(Ether* edev)
+{
+	Ctlr *ctlr;
+	static char zea[Eaddrlen];
+
+	if(smcctlrhead == nil)
+		smcpci();
+
+	/*
+	 * Any adapter matches if no edev->port is supplied,
+	 * otherwise the ports must match.
+	 */
+	for(ctlr = smcctlrhead; ctlr != nil; ctlr = ctlr->next){
+		if(ctlr->active)
+			continue;
+		if(edev->port == 0 || edev->port == ctlr->port){
+			ctlr->active = 1;
+			break;
+		}
+	}
+	if(ctlr == nil)
+		return -1;
+
+	edev->ctlr = ctlr;
+	ctlr->edev = edev;			/* point back to Ether* */
+	edev->port = ctlr->port;
+	edev->irq = 34;
+// TODO: verify speed (100Mb/s) and duplicity (full-duplex)
+	edev->mbps = 100;
+
+	/* don't overwrite existing ea */
+	if (memcmp(edev->ea, zea, Eaddrlen) == 0)
+		memmove(edev->ea, ctlr->ra, Eaddrlen);
+
+	/*
+	 * Linkage to the generic ethernet driver.
+	 */
+	edev->attach = smcattach;
+	edev->transmit = smctransmitcall;
+	edev->interrupt = smcinterrupt;
+	edev->ifstat = smcifstat;
+/*	edev->ctl = smcctl;			/* no ctl msgs supported */
+
+	edev->arg = edev;
+	edev->promiscuous = smcpromiscuous;
+	edev->multicast = smcmulticast;
+	edev->shutdown = smcshutdown;
+	return 0;
+}
+
+void
+ether9221link(void)
+{
+	addethercard("9221", smcpnp);
+}

+ 41 - 0
sys/src/9/omap/etherif.h

@@ -0,0 +1,41 @@
+enum
+{
+	MaxEther	= 4,
+	Ntypes		= 8,
+};
+
+typedef struct Ether Ether;
+struct Ether {
+	RWlock;
+	ISAConf;			/* hardware info */
+
+	int	ctlrno;
+	int	minmtu;
+	int 	maxmtu;
+
+	Netif;
+
+	void	(*attach)(Ether*);	/* filled in by reset routine */
+	void	(*detach)(Ether*);
+	void	(*transmit)(Ether*);
+	void	(*interrupt)(Ureg*, void*);
+	long	(*ifstat)(Ether*, void*, long, ulong);
+	long 	(*ctl)(Ether*, void*, long); /* custom ctl messages */
+	void	(*power)(Ether*, int);	/* power on/off */
+	void	(*shutdown)(Ether*);	/* shutdown hardware before reboot */
+
+	void*	ctlr;
+	uchar	ea[Eaddrlen];
+	void*	address;
+	int	irq;
+
+	Queue*	oq;
+};
+
+extern Block* etheriq(Ether*, Block*, int);
+extern void addethercard(char*, int(*)(Ether*));
+extern ulong ethercrc(uchar*, int);
+extern int parseether(uchar*, char*);
+
+#define NEXT(x, l)	(((x)+1)%(l))
+#define PREV(x, l)	(((x) == 0) ? (l)-1: (x)-1)

+ 173 - 0
sys/src/9/omap/fns.h

@@ -0,0 +1,173 @@
+#define checkmmu(a, b)
+#define countpagerefs(a, b)
+
+#include "../port/portfns.h"
+
+extern int led(int, int);
+extern void ledexit(int);
+extern void delay(int);
+extern void _uartputs(char*, int);
+extern int _uartprint(char*, ...);
+
+#pragma	varargck argpos	_uartprint 1
+
+extern void archreboot(void);
+extern void archreset(void);
+extern void cachedinv(void);
+extern void cachedinvse(void*, int);
+extern void cachedwb(void);
+extern void cachedwbinv(void);
+extern void cachedwbinvse(void*, int);
+extern void cachedwbse(void*, int);
+extern void cacheiinv(void);
+extern void cacheinfo(int level, Memcache *cp);
+extern void cacheuwbinv(void);
+extern uintptr cankaddr(uintptr pa);
+extern void chkmissing(void);
+extern void clockshutdown(void);
+extern int clz(ulong);
+extern int cmpswap(long*, long, long);
+extern void coherence(void);
+extern u32int controlget(void);
+extern u32int cpctget(void);
+extern u32int cpidget(void);
+extern ulong cprd(int cp, int op1, int crn, int crm, int op2);
+extern ulong cprdsc(int op1, int crn, int crm, int op2);
+extern void cpuidprint(void);
+extern void cpwr(int cp, int op1, int crn, int crm, int op2, ulong val);
+extern void cpwrsc(int op1, int crn, int crm, int op2, ulong val);
+#define cycles(ip) *(ip) = lcycles()
+extern u32int dacget(void);
+extern void dacput(u32int);
+extern void dmainit(void);
+extern int dmastart(void *, int, void *, int, uint, Rendez *, int *);
+extern void dmatest(void);
+extern u32int farget(void);
+extern ulong fprd(int fpreg);
+extern void fpwr(int fpreg, ulong val);
+extern u32int fsrget(void);
+extern u32int getscr(void);
+extern u32int getpsr(void);
+extern ulong getwayssets(void);
+extern void intrsoff(void);
+extern int isaconfig(char*, int, ISAConf*);
+extern int isdmadone(int);
+extern int ispow2(uvlong);
+extern void l2cacheuinv(void);
+extern void l2cacheuwb(void);
+extern void l2cacheuwbinv(void);
+extern void lastresortprint(char *buf, long bp);
+extern int log2(ulong);
+extern void machinit(void);
+extern void mmuinvalidate(void);		/* 'mmu' or 'tlb'? */
+extern void mmuinvalidateaddr(u32int);		/* 'mmu' or 'tlb'? */
+extern u32int pidget(void);
+extern void pidput(u32int);
+extern vlong probeaddr(uintptr);
+extern void procrestore(Proc *);
+extern void procsave(Proc*);
+extern void procsetup(Proc*);
+extern void _reset(void);
+extern void serialputs(char* s, int n);
+extern void setcachelvl(int);
+extern void setr13(int, u32int*);
+#define tas _tas
+extern int _tas(void *);
+extern u32int ttbget(void);
+extern void ttbput(u32int);
+extern void watchdoginit(void);
+
+extern int irqenable(int, void (*)(Ureg*, void*), void*, char*);
+extern int irqdisable(int, void (*)(Ureg*, void*), void*, char*);
+#define intrenable(i, f, a, b, n)	irqenable((i), (f), (a), (n))
+#define intrdisable(i, f, a, b, n)	irqdisable((i), (f), (a), (n))
+extern void vectors(void);
+extern void vtable(void);
+
+/* dregs, going away */
+extern int inb(int);
+extern void outb(int, int);
+
+/*
+ * Things called in main.
+ */
+extern void archconfinit(void);
+extern void clockinit(void);
+extern void i8250console(void);
+extern void links(void);
+extern void mmuinit(void);
+extern void touser(uintptr);
+extern void trapinit(void);
+
+
+extern int fpiarm(Ureg*);
+extern int fpudevprocio(Proc*, void*, long, uintptr, int);
+extern void fpuinit(void);
+extern void fpunoted(void);
+extern void fpunotify(Ureg*);
+extern void fpuprocrestore(Proc*);
+extern void fpuprocsave(Proc*);
+extern void fpusysprocsetup(Proc*);
+extern void fpusysrfork(Ureg*);
+extern void fpusysrforkchild(Proc*, Proc*);
+extern int fpuemu(Ureg*);
+
+/*
+ * Miscellaneous machine dependent stuff.
+ */
+extern char* getenv(char*, char*, int);
+char*	getconf(char*);
+uintptr mmukmap(uintptr, uintptr, usize);
+uintptr mmukunmap(uintptr, uintptr, usize);
+extern void* mmuuncache(void*, usize);
+extern void* ucalloc(usize);
+extern Block* ucallocb(int);
+extern void* ucallocalign(usize size, int align, int span);
+extern void ucfree(void*);
+extern void ucfreeb(Block*);
+
+/*
+ * Things called from port.
+ */
+extern void delay(int);				/* only scheddump() */
+extern int islo(void);
+extern void microdelay(int);			/* only edf.c */
+extern void evenaddr(uintptr);
+extern void idlehands(void);
+extern void setkernur(Ureg*, Proc*);		/* only devproc.c */
+extern void* sysexecregs(uintptr, ulong, int);
+extern void sysprocsetup(Proc*);
+
+/*
+ * PCI stuff.
+ */
+
+int	cas32(void*, u32int, u32int);
+int	tas32(void*);
+
+#define CASU(p, e, n)	cas32((p), (u32int)(e), (u32int)(n))
+#define CASV(p, e, n)	cas32((p), (u32int)(e), (u32int)(n))
+#define CASW(addr, exp, new)	cas32((addr), (exp), (new))
+#define TAS(addr)	tas32(addr)
+
+extern void forkret(void);
+extern int userureg(Ureg*);
+void*	vmap(uintptr, usize);
+void	vunmap(void*, usize);
+
+extern void kexit(Ureg*);
+
+#define	getpgcolor(a)	0
+#define	kmapinval()
+
+#define PTR2UINT(p)	((uintptr)(p))
+#define UINT2PTR(i)	((void*)(i))
+
+#define	waserror()	(up->nerrlab++, setlabel(&up->errlab[up->nerrlab-1]))
+
+#define KADDR(pa)	UINT2PTR(KZERO    | ((uintptr)(pa) & ~KSEGM))
+#define PADDR(va)	PTR2UINT(PHYSDRAM | ((uintptr)(va) & ~KSEGM))
+
+#define wave(c) *(ulong *)PHYSCONS = (c)
+
+#define MASK(v)	((1UL << (v)) - 1)	/* mask `v' bits wide */

+ 0 - 0
sys/src/9/omap/io.h


+ 586 - 0
sys/src/9/omap/l.s

@@ -0,0 +1,586 @@
+/*
+ * ti omap3530 SoC machine assist
+ * arm cortex-a8 processor
+ *
+ * loader uses R11 as scratch.
+ * R9 and R10 are used for `extern register' variables.
+ */
+
+#include "arm.s"
+
+/*
+ * MCR and MRC are counter-intuitively named.
+ *	MCR	coproc, opcode1, Rd, CRn, CRm[, opcode2]	# arm -> coproc
+ *	MRC	coproc, opcode1, Rd, CRn, CRm[, opcode2]	# coproc -> arm
+ */
+
+/*
+ * Entered here from Das U-Boot or another Plan 9 kernel with MMU disabled.
+ * Until the MMU is enabled it is OK to call functions provided
+ * they are within ±32MiB relative and do not require any
+ * local variables or more than one argument (i.e. there is
+ * no stack).
+ */
+TEXT _start(SB), 1, $-4
+	MOVW	$setR12(SB), R12		/* load the SB */
+	SUB	$KZERO, R12
+	ADD	$PHYSDRAM, R12
+
+	/* SVC mode, interrupts disabled */
+	MOVW	$(PsrDirq|PsrDfiq|PsrMsvc), R1
+	MOVW	R1, CPSR
+	BARRIERS
+
+	DELAY(printloopret, 1)
+WAVE('\r')
+	DELAY(printloopnl, 1)
+WAVE('\n')
+	/*
+	 * work around errata
+	 */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	ORR	$(CpACissue1|CpACldstissue1), R1  /* fight omap35x errata 3.1.1.9 */
+	ORR	$CpACibe, R1			/* enable cp15 invalidate */
+	ORR	$CpACl1pe, R1			/* enable l1 parity checking */
+	ORR	$CpCalign, R1			/* catch alignment errors */
+	BIC	$CpACasa, R1			/* no speculative accesses */
+	/* go faster with fewer restrictions */
+	BIC	$(CpACcachenopipe|CpACcp15serial|CpACcp15waitidle|CpACcp15pipeflush), R1
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	BARRIERS
+
+	MRC	CpSC, 1, R1, C(CpCLD), C(CpCLDl2), CpCLDl2aux
+	ORR	$CpCl2nowralloc, R1		/* fight cortex errata 460075 */
+	ORR	$(CpCl2ecc|CpCl2eccparity), R1
+#ifdef TEDIUM
+	/*
+	 * I don't know why this clobbers the system, but I'm tired
+	 * of arguing with this fussy processor.  To hell with it.
+	 */
+	MCR	CpSC, 1, R1, C(CpCLD), C(CpCLDl2), CpCLDl2aux
+	BARRIERS
+#endif
+	DELAY(printloops, 1)
+WAVE('P')
+	/*
+	 * disable the MMU & caches
+	 */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BIC	$(CpCdcache|CpCicache|CpCmmu), R1
+	ORR	$CpCsbo, R1
+	BIC	$CpCsbz, R1
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	BIC	$CpACl2en, R1			/* turn l2 cache off */
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	BARRIERS
+
+WAVE('l')
+	DELAY(printloop3, 1)
+
+WAVE('a')
+	/* clear Mach */
+	MOVW	$PADDR(MACHADDR), R4		/* address of Mach */
+	MOVW	$0, R0
+_machZ:
+	MOVW	R0, (R4)
+	ADD	$4, R4
+	CMP.S	$PADDR(L1+L1X(0)), R4	/* end at top-level page table */
+	BNE	_machZ
+
+	/*
+	 * set up the MMU page table
+	 */
+
+WAVE('n')
+	/* clear all PTEs first, to provide a default */
+//	MOVW	$PADDR(L1+L1X(0)), R4		/* address of PTE for 0 */
+_ptenv0:
+	ZEROPTE()
+	CMP.S	$PADDR(L1+16*KiB), R4
+	BNE	_ptenv0
+
+	DELAY(printloop4, 2)
+WAVE(' ')
+	/*
+	 * set up double map of PHYSDRAM, KZERO to PHYSDRAM for first few MBs,
+	 * but only if KZERO and PHYSDRAM differ.
+	 */
+	MOVW	$PTEDRAM, R2			/* PTE bits */
+	MOVW	$PHYSDRAM, R3			/* pa */
+	CMP	$KZERO, R3
+	BEQ	no2map
+	MOVW	$PADDR(L1+L1X(PHYSDRAM)), R4  /* address of PTE for PHYSDRAM */
+	MOVW	$DOUBLEMAPMBS, R5
+_ptdbl:
+	FILLPTE()
+	SUB.S	$1, R5
+	BNE	_ptdbl
+no2map:
+
+	/*
+	 * back up and fill in PTEs for memory at KZERO.
+	 * beagle has 1 bank of 256MB of SDRAM at PHYSDRAM;
+	 * igepv2 has 1 bank of 512MB at PHYSDRAM.
+	 * Map the maximum (512MB).
+	 */
+WAVE('9')
+	MOVW	$PTEDRAM, R2			/* PTE bits */
+	MOVW	$PHYSDRAM, R3
+	MOVW	$PADDR(L1+L1X(KZERO)), R4	/* start with PTE for KZERO */
+	MOVW	$512, R5			/* inner loop count (MBs) */
+_ptekrw:					/* set PTEs */
+	FILLPTE()
+	SUB.S	$1, R5				/* decrement inner loop count */
+	BNE	_ptekrw
+
+	/*
+	 * back up and fill in PTEs for MMIO
+	 * stop somewhere after uarts
+	 */
+WAVE(' ')
+	MOVW	$PTEIO, R2			/* PTE bits */
+	MOVW	$PHYSIO, R3
+	MOVW	$PADDR(L1+L1X(VIRTIO)), R4	/* start with PTE for VIRTIO */
+_ptenv2:
+	FILLPTE()
+	CMP.S	$PADDR(L1+L1X(PHYSIOEND)), R4
+	BNE	_ptenv2
+
+	/* mmu.c sets up the trap vectors later */
+
+	/*
+	 * set up a temporary stack; avoid data & bss segments
+	 */
+	MOVW	$(PHYSDRAM | (128*1024*1024)), R13
+
+	/* invalidate caches */
+	BL	cachedinv(SB)
+	MOVW	$KZERO, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall
+	BARRIERS
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait
+	BARRIERS
+
+WAVE('f')
+	/*
+	 * turn caches on
+	 */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	ORR	$CpACl2en, R1			/* turn l2 cache on */
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	BARRIERS
+
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	ORR	$(CpCdcache|CpCicache), R1
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+
+WAVE('r')
+	/* set the domain access control */
+	MOVW	$Client, R0
+	BL	dacput(SB)
+
+	DELAY(printloop5, 2)
+WAVE('o')
+	/* set the translation table base */
+	MOVW	$PADDR(L1), R0
+	BL	ttbput(SB)
+
+	MOVW	$0, R0
+	BL	pidput(SB)		/* paranoia */
+
+WAVE('m')
+	/*
+	 * the little dance to turn the MMU on
+	 */
+	BL	cacheuwbinv(SB)
+	BL	mmuinvalidate(SB)
+	BL	mmuenable(SB)
+
+WAVE(' ')
+	/* warp the PC into the virtual map */
+	MOVW	$KZERO, R0
+	BL	_r15warp(SB)
+
+	/*
+	 * now running at KZERO+something!
+	 */
+
+	MOVW	$setR12(SB), R12		/* reload the SB */
+
+	/*
+	 * set up temporary stack again, in case we've just switched
+	 * to a new register set.
+	 */
+	MOVW	$(KZERO|(128*1024*1024)), R13
+
+	/* can now execute arbitrary C code */
+
+	BL	cacheuwbinv(SB)
+
+WAVE('B')
+	MOVW	$PHYSDRAM, R3			/* pa */
+	CMP	$KZERO, R3
+	BEQ	no2unmap
+	/* undo double map of PHYSDRAM, KZERO & first few MBs */
+	MOVW	$(L1+L1X(PHYSDRAM)), R4		/* addr. of PTE for PHYSDRAM */
+	MOVW	$0, R0
+	MOVW	$DOUBLEMAPMBS, R5
+_ptudbl:
+	ZEROPTE()
+	SUB.S	$1, R5
+	BNE	_ptudbl
+no2unmap:
+	BARRIERS
+	MOVW	$KZERO, R0
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+
+#ifdef HIGH_SECURITY				/* i.e., not GP omap */
+	/* hack: set `secure monitor' vector base addr for cortex */
+//	MOVW	$HVECTORS, R0
+	MOVW	$PADDR(L1), R0
+	SUB	$(MACHSIZE+(2*1024)), R0
+	MCR	CpSC, 0, R0, C(CpVECS), C(CpVECSbase), CpVECSmon
+	BARRIERS
+#endif
+
+	/*
+	 * call main in C
+	 * pass Mach to main and set up the stack in it
+	 */
+	MOVW	$(MACHADDR), R0			/* Mach */
+	MOVW	R0, R13
+	ADD	$(MACHSIZE), R13		/* stack pointer */
+	SUB	$4, R13				/* space for link register */
+	MOVW	R0, R10				/* m = MACHADDR */
+WAVE('e')
+	BL	main(SB)			/* void main(Mach*) */
+	/*FALLTHROUGH*/
+
+/*
+ * reset the system
+ */
+
+TEXT _reset(SB), 1, $-4
+	MOVW	$(PsrDirq|PsrDfiq|PsrMsvc), R0
+	MOVW	R0, CPSR
+	BARRIERS
+
+	DELAY(printloopr, 2)
+WAVE('!')
+WAVE('r')
+WAVE('e')
+WAVE('s')
+WAVE('e')
+WAVE('t')
+WAVE('!')
+WAVE('\r')
+WAVE('\n')
+
+	/* turn the caches off */
+	BL	cacheuwbinv(SB)
+
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	BIC	$(CpCicache|CpCdcache|CpCalign), R0
+	ORR	$CpCsw, R0			/* enable SWP */
+	MCR	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+
+	/* redo double map of PHYSDRAM, KZERO & first few MBs */
+	MOVW	$PTEDRAM, R2			/* PTE bits */
+	MOVW	$PHYSDRAM, R3			/* pa */
+	MOVW	$(L1+L1X(PHYSDRAM)), R4		/* address of PHYSDRAM's PTE */
+	MOVW	$DOUBLEMAPMBS, R5
+_ptrdbl:
+	FILLPTE()
+	SUB.S	$1, R5
+	BNE	_ptrdbl
+
+	MOVW	$PHYSDRAM, R0
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+
+	/* turn the MMU off */
+	MOVW	$PHYSDRAM, R0
+	BL	_r15warp(SB)
+	BL	mmuinvalidate(SB)
+	BL	mmudisable(SB)
+
+	/* set new reset vector */
+	MOVW	$HVECTORS, R2
+	MOVW	$0xe59ff018, R3			/* MOVW 0x18(R15), R15 */
+	MOVW	R3, (R2)
+	BARRIERS
+
+//	MOVW	$PHYSFLASH, R3			/* TODO */
+//	MOVW	R3, 0x20(R2)			/* where $0xe59ff018 jumps to */
+
+	/* ...and jump to it */
+//	MOVW	R2, R15				/* software reboot */
+_limbo:						/* should not get here... */
+	BL	_idlehands(SB)
+	B	_limbo				/* ... and can't get out */
+	BL	_div(SB)			/* hack to load _div, etc. */
+
+TEXT _r15warp(SB), 1, $-4
+	BIC	$KSEGM, R14			/* link reg, will become PC */
+	ORR	R0, R14
+	BIC	$KSEGM, R13			/* SP too */
+	ORR	R0, R13
+	RET
+
+/*
+ * `single-element' cache operations.
+ * in arm arch v7, they operate on all cache levels, so separate
+ * l2 functions are unnecessary.
+ */
+
+TEXT cachedwbse(SB), 1, $-4			/* D writeback SE */
+	MOVW	R0, R2
+
+	MOVW	CPSR, R3			/* splhi */
+	ORR	$(PsrDirq), R3, R1
+	MOVW	R1, CPSR
+	BARRIERS
+
+	MOVW	R2, R0
+	MOVW	4(FP), R1
+	ADD	R0, R1				/* R1 is end address */
+	BIC	$(CACHELINESZ-1), R0		/* cache line start */
+_dwbse:
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEse
+	/* can't have a BARRIER here since it zeroes R0 */
+	ADD	$CACHELINESZ, R0
+	CMP.S	R0, R1
+	BGT	_dwbse
+	B	_wait
+
+TEXT cachedwbinvse(SB), 1, $-4			/* D writeback+invalidate SE */
+	MOVW	R0, R2
+
+	MOVW	CPSR, R3			/* splhi */
+	ORR	$(PsrDirq), R3, R1
+	MOVW	R1, CPSR
+	BARRIERS
+
+	MOVW	R2, R0
+	MOVW	4(FP), R1
+	ADD	R0, R1				/* R1 is end address */
+	BIC	$(CACHELINESZ-1), R0		/* cache line start */
+_dwbinvse:
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEse
+	/* can't have a BARRIER here since it zeroes R0 */
+	ADD	$CACHELINESZ, R0
+	CMP.S	R0, R1
+	BGT	_dwbinvse
+_wait:						/* drain write buffer */
+	BARRIERS
+	/* drain L1 write buffer, also drains L2 eviction buffer on sheeva */
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait
+	BARRIERS
+
+	MOVW	R3, CPSR			/* splx */
+	BARRIERS
+	RET
+
+TEXT cachedinvse(SB), 1, $-4			/* D invalidate SE */
+	MOVW	R0, R2
+
+	MOVW	CPSR, R3			/* splhi */
+	ORR	$(PsrDirq), R3, R1
+	MOVW	R1, CPSR
+	BARRIERS
+
+	MOVW	R2, R0
+	MOVW	4(FP), R1
+	ADD	R0, R1				/* R1 is end address */
+	BIC	$(CACHELINESZ-1), R0		/* cache line start */
+_dinvse:
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvd), CpCACHEse
+	/* can't have a BARRIER here since it zeroes R0 */
+	ADD	$CACHELINESZ, R0
+	CMP.S	R0, R1
+	BGT	_dinvse
+	B	_wait
+
+/*
+ *  enable mmu and high vectors
+ */
+TEXT mmuenable(SB), 1, $-4
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	ORR	$(CpChv|CpCmmu), R0
+	MCR	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+	RET
+
+TEXT mmudisable(SB), 1, $-4
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	BIC	$(CpChv|CpCmmu), R0
+	MCR	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+	RET
+
+/*
+ * If one of these MCR instructions crashes or hangs the machine,
+ * check your Level 1 page table (at TTB) closely.
+ */
+TEXT mmuinvalidate(SB), 1, $-4			/* invalidate all */
+	BARRIERS
+	MOVW	CPSR, R2
+	ORR	$(PsrDirq|PsrDfiq), R2, R3	/* interrupts off */
+	MOVW	R3, CPSR
+	BARRIERS
+
+	MOVW	PC, R0				/* some valid virtual address */
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+	MOVW	R2, CPSR			/* interrupts restored */
+	BARRIERS
+	RET
+
+TEXT mmuinvalidateaddr(SB), 1, $-4		/* invalidate single entry */
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinvse
+	BARRIERS
+	RET
+
+TEXT cpidget(SB), 1, $-4			/* main ID */
+	MRC	CpSC, 0, R0, C(CpID), C(0), CpIDid
+	RET
+
+TEXT cpctget(SB), 1, $-4			/* cache type */
+	MRC	CpSC, 0, R0, C(CpID), C(0), CpIDct
+	RET
+
+TEXT controlget(SB), 1, $-4			/* control */
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	RET
+
+TEXT ttbget(SB), 1, $-4				/* translation table base */
+	MRC	CpSC, 0, R0, C(CpTTB), C(0), CpTTB0
+	RET
+
+TEXT ttbput(SB), 1, $-4				/* translation table base */
+	MCR	CpSC, 0, R0, C(CpTTB), C(0), CpTTB0
+	MCR	CpSC, 0, R0, C(CpTTB), C(0), CpTTB1	/* cortex has two */
+	BARRIERS
+	RET
+
+TEXT dacget(SB), 1, $-4				/* domain access control */
+	MRC	CpSC, 0, R0, C(CpDAC), C(0)
+	RET
+
+TEXT dacput(SB), 1, $-4				/* domain access control */
+	MCR	CpSC, 0, R0, C(CpDAC), C(0)
+	BARRIERS
+	RET
+
+TEXT fsrget(SB), 1, $-4				/* fault status */
+	MRC	CpSC, 0, R0, C(CpFSR), C(0)
+	RET
+
+TEXT farget(SB), 1, $-4				/* fault address */
+	MRC	CpSC, 0, R0, C(CpFAR), C(0x0)
+	RET
+
+TEXT getpsr(SB), 1, $-4
+	MOVW	CPSR, R0
+	RET
+
+TEXT getscr(SB), 1, $-4
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(CpCONTROLscr), CpSCRscr
+	RET
+
+TEXT pidget(SB), 1, $-4				/* address translation pid */
+	MRC	CpSC, 0, R0, C(CpPID), C(0x0)
+	RET
+
+TEXT pidput(SB), 1, $-4				/* address translation pid */
+	MCR	CpSC, 0, R0, C(CpPID), C(0x0)
+	BARRIERS
+	RET
+
+TEXT splhi(SB), 1, $-4
+	MOVW	CPSR, R3			/* turn off interrupts */
+	ORR	$(PsrDirq|PsrDfiq), R3, R1
+	MOVW	R1, CPSR
+	BARRIERS
+
+	MOVW	$(MACHADDR+4), R2		/* save caller pc in Mach */
+	MOVW	R14, 0(R2)
+
+	MOVW	R3, R0				/* must return old CPSR */
+	RET
+
+TEXT spllo(SB), 1, $-4
+	MOVW	CPSR, R3
+	BIC	$PsrDirq, R3, R1
+	MOVW	R1, CPSR
+	BARRIERS
+
+	MOVW	R3, R0				/* must return old CPSR */
+	RET
+
+TEXT splx(SB), 1, $-4
+	MOVW	$(MACHADDR+0x04), R2		/* save caller pc in Mach */
+	MOVW	R14, 0(R2)
+
+	MOVW	CPSR, R3
+	MOVW	R0, CPSR			/* reset interrupt level */
+	BARRIERS
+
+	MOVW	R3, R0				/* must return old CPSR */
+	RET
+
+TEXT spldone(SB), 1, $0				/* marker for devkprof.c */
+	RET
+
+TEXT islo(SB), 1, $-4
+	MOVW	CPSR, R0
+	AND	$(PsrDirq), R0
+	EOR	$(PsrDirq), R0
+	RET
+
+TEXT	_tas(SB), $-4
+	MOVW	R0,R1
+	BARRIERS
+	MOVW	$1,R0
+	SWPW	R0,(R1)			/* fix: deprecated in armv7 */
+	MOVW	R0, R3
+	BARRIERS
+	MOVW	R3, R0
+	RET
+
+TEXT clz(SB), 1, $-4
+	CLZ(0, 0)			/* 0 is R0 */
+	RET
+
+TEXT setlabel(SB), 1, $-4
+	MOVW	R13, 0(R0)		/* sp */
+	MOVW	R14, 4(R0)		/* pc */
+	MOVW	$0, R0
+	RET
+
+TEXT gotolabel(SB), 1, $-4
+	MOVW	0(R0), R13		/* sp */
+	MOVW	4(R0), R14		/* pc */
+	MOVW	$1, R0
+	RET
+
+TEXT getcallerpc(SB), 1, $-4
+	MOVW	0(R13), R0
+	RET
+
+TEXT _idlehands(SB), 1, $-4
+	BARRIERS
+	WFI
+	RET
+
+TEXT coherence(SB), 1, $-4
+	BARRIERS
+	RET
+
+#include "cache.v7.s"

+ 666 - 0
sys/src/9/omap/main.c

@@ -0,0 +1,666 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+#include "init.h"
+#include <pool.h>
+
+#include "reboot.h"
+
+/*
+ * Where configuration info is left for the loaded programme.
+ * This will turn into a structure as more is done by the boot loader
+ * (e.g. why parse the .ini file twice?).
+ * There are 3584 bytes available at CONFADDR.
+ */
+#define BOOTARGS	((char*)CONFADDR)
+#define	BOOTARGSLEN	(16*KiB)		/* limit in devenv.c */
+#define	MAXCONF		64
+#define MAXCONFLINE	160
+
+#define isascii(c) ((uchar)(c) > 0 && (uchar)(c) < 0177)
+
+uintptr kseg0 = KZERO;
+Mach* machaddr[MAXMACH];
+
+/*
+ * Option arguments from the command line.
+ * oargv[0] is the boot file.
+ * Optionsinit() is called from multiboot()
+ * or some other machine-dependent place
+ * to set it all up.
+ */
+static int oargc;
+static char* oargv[20];
+static char oargb[128];
+static int oargblen;
+static char oenv[4096];
+
+static uintptr sp;		/* XXX - must go - user stack of init proc */
+
+int vflag;
+int normalprint;
+char debug[256];
+
+/* store plan9.ini contents here at least until we stash them in #ec */
+static char confname[MAXCONF][KNAMELEN];
+static char confval[MAXCONF][MAXCONFLINE];
+static int nconf;
+
+static int
+findconf(char *name)
+{
+	int i;
+
+	for(i = 0; i < nconf; i++)
+		if(cistrcmp(confname[i], name) == 0)
+			return i;
+	return -1;
+}
+
+char*
+getconf(char *name)
+{
+	int i;
+
+	i = findconf(name);
+	if(i >= 0)
+		return confval[i];
+	return nil;
+}
+
+void
+addconf(char *name, char *val)
+{
+	int i;
+
+	i = findconf(name);
+	if(i < 0){
+		if(val == nil || nconf >= MAXCONF)
+			return;
+		i = nconf++;
+		strecpy(confname[i], confname[i]+sizeof(confname[i]), name);
+	}
+//	confval[i] = val;
+	strecpy(confval[i], confval[i]+sizeof(confval[i]), val);
+}
+
+static void
+writeconf(void)
+{
+	char *p, *q;
+	int n;
+
+	p = getconfenv();
+
+	if(waserror()) {
+		free(p);
+		nexterror();
+	}
+
+	/* convert to name=value\n format */
+	for(q=p; *q; q++) {
+		q += strlen(q);
+		*q = '=';
+		q += strlen(q);
+		*q = '\n';
+	}
+	n = q - p + 1;
+	if(n >= BOOTARGSLEN)
+		error("kernel configuration too large");
+	memmove(BOOTARGS, p, n);
+	poperror();
+	free(p);
+}
+
+/*
+ * assumes that we have loaded our /cfg/pxe/mac file at 0x1000 with
+ * tftp in u-boot.  no longer uses malloc, so can be called early.
+ */
+static void
+plan9iniinit(void)
+{
+	char *k, *v, *next;
+
+	k = (char *)CONFADDR;
+	if(!isascii(*k))
+		return;
+
+	for(; k && *k != '\0'; k = next) {
+		if (!isascii(*k))		/* sanity check */
+			break;
+		next = strchr(k, '\n');
+		if (next)
+			*next++ = '\0';
+
+		if (*k == '\0' || *k == '\n' || *k == '#')
+			continue;
+		v = strchr(k, '=');
+		if(v == nil)
+			continue;		/* mal-formed line */
+		*v++ = '\0';
+
+		addconf(k, v);
+	}
+}
+
+static void
+optionsinit(char* s)
+{
+	char *o;
+
+	strcpy(oenv, "");
+	o = strecpy(oargb, oargb+sizeof(oargb), s)+1;
+	if(getenv("bootargs", o, o - oargb) != nil)
+		*(o-1) = ' ';
+
+	oargblen = strlen(oargb);
+	oargc = tokenize(oargb, oargv, nelem(oargv)-1);
+	oargv[oargc] = nil;
+}
+
+char*
+getenv(char* name, char* buf, int n)
+{
+	char *e, *p, *q;
+
+	p = oenv;
+	while(*p != 0){
+		if((e = strchr(p, '=')) == nil)
+			break;
+		for(q = name; p < e; p++){
+			if(*p != *q)
+				break;
+			q++;
+		}
+		if(p == e && *q == 0){
+			strecpy(buf, buf+n, e+1);
+			return buf;
+		}
+		p += strlen(p)+1;
+	}
+
+	return nil;
+}
+
+void
+main(void)
+{
+//	int i;
+	extern char bdata[], edata[], end[], etext[];
+	static ulong vfy = 0xcafebabe;
+
+	/* l.s has already printed "Plan 9 from Be" */
+//	m = mach;					/* now done in l.s */
+
+	/* realign data seg; apparently -H0 -R4096 does not pad the text seg */
+	if (vfy != 0xcafebabe) {
+//		wave('<'); wave('-');
+		memmove(bdata, etext, edata - bdata);
+	}
+	/*
+	 * once data segment is in place, always zero bss since we may
+	 * have been loaded by another Plan 9 kernel.
+	 */
+	memset(edata, 0, end - edata);		/* zero BSS */
+	cacheuwbinv();
+	l2cacheuwbinv();
+
+	if (vfy != 0xcafebabe)
+		panic("data segment misaligned");
+	vfy = 0;
+
+wave('l');
+	machinit();
+	mmuinit();
+
+	optionsinit("/boot/boot boot");
+	quotefmtinstall();
+
+	/* want plan9.ini to be able to affect memory sizing in confinit */
+	plan9iniinit();		/* before we step on plan9.ini in low memory */
+
+	confinit();
+	/* xinit prints (if it can), so finish up the banner here. */
+	delay(10);
+	iprint("l Labs\n\n");
+	delay(10);
+	xinit();
+
+	/*
+	 * Printinit will cause the first malloc call.
+	 * (printinit->qopen->malloc) unless any of the
+	 * above (like clockinit) do an irqenable, which
+	 * will call malloc.
+	 * If the system dies here it's probably due
+	 * to malloc(->xalloc) not being initialised
+	 * correctly, or the data segment is misaligned
+	 * (it's amazing how far you can get with
+	 * things like that completely broken).
+	 *
+	 * (Should be) boilerplate from here on.
+	 */
+	trapinit();
+
+	archconfinit();
+
+	archreset();			/* configures clock signals */
+	clockinit();			/* start clocks */
+	timersinit();
+	watchdoginit();
+
+	printinit();
+
+	procinit0();
+	initseg();
+
+	dmainit();
+	links();
+	chandevreset();
+
+	i8250console();
+//	uartconsole(CONSOLE, "");		/* was "b115200" */
+//	normalprint = 1;
+	iprint("\n");
+
+	delay(20);
+	cpuidprint();
+//	chkmissing();
+
+	pageinit();
+	swapinit();
+	userinit();
+	schedinit();
+}
+
+void
+machinit(void)
+{
+	if (m == 0)
+		wave('?');
+//	memset(m, 0, sizeof(Mach));	/* done by l.s, now contains stack */
+	m->machno = 0;
+	machaddr[m->machno] = m;
+
+	m->ticks = 1;
+	m->perf.period = 1;
+
+	conf.nmach = 1;
+
+	active.machs = 1;
+	active.exiting = 0;
+
+	up = nil;
+}
+
+static void
+shutdown(int ispanic)
+{
+	int ms, once;
+
+	lock(&active);
+	if(ispanic)
+		active.ispanic = ispanic;
+	else if(m->machno == 0 && (active.machs & (1<<m->machno)) == 0)
+		active.ispanic = 0;
+	once = active.machs & (1<<m->machno);
+	active.machs &= ~(1<<m->machno);
+	active.exiting = 1;
+	unlock(&active);
+
+	if(once)
+		iprint("cpu%d: exiting\n", m->machno);
+	spllo();
+	for(ms = 5*1000; ms > 0; ms -= TK2MS(2)){
+		delay(TK2MS(2));
+		if(active.machs == 0 && consactive() == 0)
+			break;
+	}
+	delay(1000);
+}
+
+/*
+ *  exit kernel either on a panic or user request
+ */
+void
+exit(int code)
+{
+	shutdown(code);
+	splhi();
+	archreboot();
+}
+
+int
+isaconfig(char *class, int ctlrno, ISAConf *isa)
+{
+	char cc[32], *p;
+	int i;
+
+	snprint(cc, sizeof cc, "%s%d", class, ctlrno);
+	p = getconf(cc);
+	if(p == nil)
+		return 0;
+
+	isa->type = "";
+	isa->nopt = tokenize(p, isa->opt, NISAOPT);
+	for(i = 0; i < isa->nopt; i++){
+		p = isa->opt[i];
+		if(cistrncmp(p, "type=", 5) == 0)
+			isa->type = p + 5;
+		else if(cistrncmp(p, "port=", 5) == 0)
+			isa->port = strtoul(p+5, &p, 0);
+		else if(cistrncmp(p, "irq=", 4) == 0)
+			isa->irq = strtoul(p+4, &p, 0);
+		else if(cistrncmp(p, "dma=", 4) == 0)
+			isa->dma = strtoul(p+4, &p, 0);
+		else if(cistrncmp(p, "mem=", 4) == 0)
+			isa->mem = strtoul(p+4, &p, 0);
+		else if(cistrncmp(p, "size=", 5) == 0)
+			isa->size = strtoul(p+5, &p, 0);
+		else if(cistrncmp(p, "freq=", 5) == 0)
+			isa->freq = strtoul(p+5, &p, 0);
+	}
+	return 1;
+}
+
+/*
+ * the new kernel is already loaded at address `code'
+ * of size `size' and entry point `entry'.
+ */
+void
+reboot(void *entry, void *code, ulong size)
+{
+	void (*f)(ulong, ulong, ulong);
+
+	print("starting reboot...");
+	writeconf();
+	shutdown(0);
+
+	/*
+	 * should be the only processor running now
+	 */
+
+	print("reboot entry %#lux code %#lux size %ld\n",
+		PADDR(entry), PADDR(code), size);
+	delay(100);
+
+	/* turn off buffered serial console */
+	serialoq = nil;
+	kprintoq = nil;
+	screenputs = nil;
+
+	/* shutdown devices */
+	chandevshutdown();
+
+	/* call off the dog */
+	clockshutdown();
+
+	splhi();
+	intrsoff();
+
+	/* setup reboot trampoline function */
+	f = (void*)REBOOTADDR;
+	memmove(f, rebootcode, sizeof(rebootcode));
+	cacheuwbinv();
+	l2cacheuwbinv();
+
+	/* off we go - never to return */
+	(*f)(PADDR(entry), PADDR(code), size);
+
+	iprint("loaded kernel returned!\n");
+	delay(1000);
+	archreboot();
+}
+
+/*
+ *  starting place for first process
+ */
+void
+init0(void)
+{
+	int i;
+	char buf[2*KNAMELEN];
+
+	up->nerrlab = 0;
+	coherence();
+	spllo();
+
+	/*
+	 * These are o.k. because rootinit is null.
+	 * Then early kproc's will have a root and dot.
+	 */
+	up->slash = namec("#/", Atodir, 0, 0);
+	pathclose(up->slash->path);
+	up->slash->path = newpath("/");
+	up->dot = cclone(up->slash);
+
+	dmatest();		/* needs `up' set, so can't do it earlier */
+	chandevinit();
+	i8250console();		/* again */
+
+	if(!waserror()){
+		snprint(buf, sizeof(buf), "%s %s", "ARM", conffile);
+		ksetenv("terminal", buf, 0);
+		ksetenv("cputype", "arm", 0);
+		if(cpuserver)
+			ksetenv("service", "cpu", 0);
+		else
+			ksetenv("service", "terminal", 0);
+
+		/* convert plan9.ini variables to #e and #ec */
+		for(i = 0; i < nconf; i++) {
+			ksetenv(confname[i], confval[i], 0);
+			ksetenv(confname[i], confval[i], 1);
+		}
+		poperror();
+	}
+	kproc("alarm", alarmkproc, 0);
+	touser(sp);
+}
+
+static void
+bootargs(uintptr base)
+{
+	int i;
+	ulong ssize;
+	char **av, *p;
+
+	/*
+	 * Push the boot args onto the stack.
+	 * The initial value of the user stack must be such
+	 * that the total used is larger than the maximum size
+	 * of the argument list checked in syscall.
+	 */
+	i = oargblen+1;
+	p = UINT2PTR(STACKALIGN(base + PGSIZE - sizeof(up->s.args) - i));
+	memmove(p, oargb, i);
+
+	/*
+	 * Now push argc and the argv pointers.
+	 * This isn't strictly correct as the code jumped to by
+	 * touser in init9.s calls startboot (port/initcode.c) which
+	 * expects arguments
+	 * 	startboot(char *argv0, char **argv)
+	 * not the usual (int argc, char* argv[]), but argv0 is
+	 * unused so it doesn't matter (at the moment...).
+	 */
+	av = (char**)(p - (oargc+2)*sizeof(char*));
+	ssize = base + PGSIZE - PTR2UINT(av);
+	*av++ = (char*)oargc;
+	for(i = 0; i < oargc; i++)
+		*av++ = (oargv[i] - oargb) + (p - base) + (USTKTOP - BY2PG);
+	*av = nil;
+
+	/*
+	 * Leave space for the return PC of the
+	 * caller of initcode.
+	 */
+	sp = USTKTOP - ssize - sizeof(void*);
+}
+
+/*
+ *  create the first process
+ */
+void
+userinit(void)
+{
+	Proc *p;
+	Segment *s;
+	KMap *k;
+	Page *pg;
+
+	/* no processes yet */
+	up = nil;
+
+	p = newproc();
+	p->pgrp = newpgrp();
+	p->egrp = smalloc(sizeof(Egrp));
+	p->egrp->ref = 1;
+	p->fgrp = dupfgrp(nil);
+	p->rgrp = newrgrp();
+	p->procmode = 0640;
+
+	kstrdup(&eve, "");
+	kstrdup(&p->text, "*init*");
+	kstrdup(&p->user, eve);
+
+	/*
+	 * Kernel Stack
+	 */
+	p->sched.pc = PTR2UINT(init0);
+	p->sched.sp = PTR2UINT(p->kstack+KSTACK-sizeof(up->s.args)-sizeof(uintptr));
+	p->sched.sp = STACKALIGN(p->sched.sp);
+
+	/*
+	 * User Stack
+	 *
+	 * Technically, newpage can't be called here because it
+	 * should only be called when in a user context as it may
+	 * try to sleep if there are no pages available, but that
+	 * shouldn't be the case here.
+	 */
+	s = newseg(SG_STACK, USTKTOP-USTKSIZE, USTKSIZE/BY2PG);
+	s->flushme++;
+	p->seg[SSEG] = s;
+	pg = newpage(1, 0, USTKTOP-BY2PG);
+	segpage(s, pg);
+	k = kmap(pg);
+	bootargs(VA(k));
+	kunmap(k);
+
+	/*
+	 * Text
+	 */
+	s = newseg(SG_TEXT, UTZERO, 1);
+	p->seg[TSEG] = s;
+	pg = newpage(1, 0, UTZERO);
+	memset(pg->cachectl, PG_TXTFLUSH, sizeof(pg->cachectl));
+	segpage(s, pg);
+	k = kmap(s->map[0]->pages[0]);
+	memmove(UINT2PTR(VA(k)), initcode, sizeof initcode);
+	kunmap(k);
+
+	ready(p);
+}
+
+Conf conf;			/* XXX - must go - gag */
+
+Confmem omapmem[nelem(conf.mem)] = {
+	/*
+	 * Memory available to Plan 9:
+	 */
+	{ .base = PHYSDRAM, .limit = PHYSDRAM + 256*MB, },
+};
+ulong memsize = 256*MB;			/* default */
+
+void
+confinit(void)
+{
+	int i;
+	ulong kpages;
+	uintptr pa;
+	char *p;
+
+	/*
+	 * Copy the physical memory configuration to Conf.mem.
+	 */
+	if(nelem(omapmem) > nelem(conf.mem)){
+		iprint("memory configuration botch\n");
+		exit(1);
+	}
+	/* plan9.ini isn't parsed yet; drat */
+	if((p = getconf("*maxmem")) != nil) {
+		memsize = strtoul(p, 0, 0) - PHYSDRAM;
+		if (memsize < 16*MB)		/* sanity */
+			memsize = 16*MB;
+	}
+
+	omapmem[0].limit = PHYSDRAM + memsize;
+	memmove(conf.mem, omapmem, sizeof(omapmem));
+
+	conf.npage = 0;
+	pa = PADDR(PGROUND(PTR2UINT(end)));
+
+	/*
+	 *  we assume that the kernel is at the beginning of one of the
+	 *  contiguous chunks of memory and fits therein.
+	 */
+	for(i=0; i<nelem(conf.mem); i++){
+		/* take kernel out of allocatable space */
+		if(pa > conf.mem[i].base && pa < conf.mem[i].limit)
+			conf.mem[i].base = pa;
+
+		conf.mem[i].npage = (conf.mem[i].limit - conf.mem[i].base)/BY2PG;
+		conf.npage += conf.mem[i].npage;
+	}
+
+	conf.upages = (conf.npage*80)/100;
+	conf.ialloc = ((conf.npage-conf.upages)/2)*BY2PG;
+
+	/* only one processor */
+	conf.nmach = 1;
+
+	/* set up other configuration parameters */
+	conf.nproc = 100 + ((conf.npage*BY2PG)/MB)*5;
+	if(cpuserver)
+		conf.nproc *= 3;
+	if(conf.nproc > 2000)
+		conf.nproc = 2000;
+	conf.nswap = conf.npage*3;
+	conf.nswppo = 4096;
+	conf.nimage = 200;
+
+	conf.copymode = 0;		/* copy on write */
+
+	/*
+	 * Guess how much is taken by the large permanent
+	 * datastructures. Mntcache and Mntrpc are not accounted for
+	 * (probably ~300KB).
+	 */
+	kpages = conf.npage - conf.upages;
+	kpages *= BY2PG;
+	kpages -= conf.upages*sizeof(Page)
+		+ conf.nproc*sizeof(Proc)
+		+ conf.nimage*sizeof(Image)
+		+ conf.nswap
+		+ conf.nswppo*sizeof(Page);
+	mainmem->maxsize = kpages;
+	if(!cpuserver)
+		/*
+		 * give terminals lots of image memory, too; the dynamic
+		 * allocation will balance the load properly, hopefully.
+		 * be careful with 32-bit overflow.
+		 */
+		imagmem->maxsize = kpages;
+
+//	archconfinit();
+}
+
+int
+cmpswap(long *addr, long old, long new)
+{
+	return cas32(addr, old, new);
+}

+ 191 - 0
sys/src/9/omap/mem.h

@@ -0,0 +1,191 @@
+/*
+ * Memory and machine-specific definitions.  Used in C and assembler.
+ */
+#define KiB		1024u			/* Kibi 0x0000000000000400 */
+#define MiB		1048576u		/* Mebi 0x0000000000100000 */
+#define GiB		1073741824u		/* Gibi 000000000040000000 */
+
+#define HOWMANY(x, y)	(((x)+((y)-1))/(y))
+#define ROUNDUP(x, y)	(HOWMANY((x), (y))*(y))	/* ceiling */
+#define ROUNDDN(x, y)	(((x)/(y))*(y))		/* floor */
+#define MIN(a, b)	((a) < (b)? (a): (b))
+#define MAX(a, b)	((a) > (b)? (a): (b))
+
+/*
+ * Not sure where these macros should go.
+ * This probably isn't right but will do for now.
+ * The macro names are problematic too.
+ */
+/*
+ * In B(o), 'o' is the bit offset in the register.
+ * For multi-bit fields use F(v, o, w) where 'v' is the value
+ * of the bit-field of width 'w' with LSb at bit offset 'o'.
+ */
+#define B(o)		(1<<(o))
+#define F(v, o, w)	(((v) & ((1<<(w))-1))<<(o))
+
+#define FCLR(d, o, w)	((d) & ~(((1<<(w))-1)<<(o)))
+#define FEXT(d, o, w)	(((d)>>(o)) & ((1<<(w))-1))
+#define FINS(d, o, w, v) (FCLR((d), (o), (w))|F((v), (o), (w)))
+#define FSET(d, o, w)	((d)|(((1<<(w))-1)<<(o)))
+
+#define FMASK(o, w)	(((1<<(w))-1)<<(o))
+
+/*
+ * Sizes
+ */
+#define	PGSIZE		(4*KiB)			/* bytes per page */
+#define	PGSHIFT		12			/* log(PGZIZE) */
+#define	PGROUND(s)	ROUNDUP(s, PGSIZE)
+#define	ROUND(s, sz)	(((s)+(sz-1))&~(sz-1))
+
+#define	MAXMACH		1			/* max # cpus system can run */
+#define	MACHSIZE	(PGSIZE)
+
+#define KSTKSIZE	(16*KiB)			/* was 8K */
+#define STACKALIGN(sp)	((sp) & ~3)		/* bug: assure with alloc */
+
+/*
+ * Address spaces.
+ * KTZERO is used by kprof and dumpstack (if any).
+ *
+ * KZERO (0xc0000000) is mapped to physical 0x80000000 (start of dram).
+ * u-boot claims to occupy the first 3 MB of dram, but we're willing to
+ * step on it once we're loaded.  Expect plan9.ini in the first 64K past 3MB.
+ *
+ * L2 PTEs are stored in 1K before Mach (11K to 12K above KZERO).
+ * cpu0's Mach struct is at L1 - MACHSIZE(4K) to L1 (12K to 16K above KZERO).
+ * L1 PTEs are stored from L1 to L1+32K (16K to 48K above KZERO).
+ * KTZERO may be anywhere after that (but probably shouldn't collide with
+ * u-boot).
+ * This should leave over 8K from KZERO to L2 PTEs.
+ */
+#define	KSEG0		0xC0000000		/* kernel segment */
+/* mask to check segment; good for 512MB dram */
+#define	KSEGM		0xE0000000
+#define	KZERO		KSEG0			/* kernel address space */
+#define L1		(KZERO+16*KiB)		/* tt ptes: 16KiB aligned */
+#define CONFADDR	(KZERO+0x300000)	/* unparsed plan9.ini */
+/* KTZERO must match loadaddr in mkfile */
+#define	KTZERO		(KZERO+0x310000)	/* kernel text start */
+
+#define	UZERO		0			/* user segment */
+#define	UTZERO		(UZERO+BY2PG)		/* user text start */
+#define UTROUND(t)	ROUNDUP((t), BY2PG)
+/* moved USTKTOP down to 512MB to keep MMIO space out of user space. */
+#define	USTKTOP		0x20000000		/* user segment end +1 */
+#define	USTKSIZE	(8*1024*1024)		/* user stack size */
+#define	TSTKTOP		(USTKTOP-USTKSIZE)	/* sysexec temporary stack */
+#define	TSTKSIZ	 	256
+
+/* address at which to copy and execute rebootcode */
+#define	REBOOTADDR	KADDR(0x100)
+
+/*
+ * Legacy...
+ */
+#define BLOCKALIGN	32			/* only used in allocb.c */
+#define KSTACK		KSTKSIZE
+
+/*
+ * Sizes
+ */
+#define BI2BY		8			/* bits per byte */
+#define BY2PG		PGSIZE
+#define BY2SE		4
+#define BY2WD		4
+#define BY2V		8			/* only used in xalloc.c */
+
+#define CACHELINESZ	64			/* bytes per cache line */
+#define	PTEMAPMEM	(1024*1024)	
+#define	PTEPERTAB	(PTEMAPMEM/BY2PG)
+#define	SEGMAPSIZE	1984			/* magic 16*124 */
+#define	SSEGMAPSIZE	16			/* magic */
+#define	PPN(x)		((x)&~(BY2PG-1))	/* pure page number? */
+
+/*
+ * With a little work these move to port.
+ */
+#define	PTEVALID	(1<<0)
+#define	PTERONLY	0
+#define	PTEWRITE	(1<<1)
+#define	PTEUNCACHED	(1<<2)
+#define PTEKERNEL	(1<<3)
+
+/*
+ * Physical machine information from here on.
+ */
+#define PHYSETHER	0x2c000000
+
+#define PHYSIO		0x48000000	/* L4 ctl */
+
+#define PHYSSCM		0x48002000	/* system control module */
+/* core control pad cfg		0x48002030—0x480021e4, */
+/* core control d2d pad cfg	0x480021e4—0x48002264 */
+#define PHYSSCMPCONF	0x48002270	/* general device config */
+#define PHYSOMAPSTS	0x4800244c	/* standalone short: has l2 size */
+/* core control pad cfg (2)	0x480025d8—0x480025fc */
+#define PHYSSWBOOTCFG	0x48002910	/* sw booting config */
+/* wakeup control pad cfg	0x48002a00—0x48002a54 */
+#define PHYSSCMMPU	0x48004900	/* actually CPU */
+#define PHYSSCMCORE	0x48004a00
+#define PHYSSCMWKUP	0x48004c00
+#define PHYSSCMPLL	0x48004d00	/* clock ctl for dpll[3-5] */
+#define PHYSSCMPER	0x48005000
+#define PHYSSCMUSB	0x48005400
+
+#define PHYSL4CORE	0x48040100	/* l4 ap */
+#define PHYSSDMA	0x48056000	/* system dma */
+#define PHYSDMA		0x48060000
+
+#define PHYSUSBTLL	0x48062000	/* usb: transceiver-less link */
+#define PHYSUHH		0x48064000	/* usb: `high-speed usb host' ctlr or subsys */
+#define PHYSOHCI	0x48064400	/* usb 1.0: slow */
+#define PHYSEHCI	0x48064800	/* usb 2.0: medium */
+#define PHYSUART0	0x4806a000
+#define PHYSUART1	0x4806c000
+#define PHYSUSBOTG	0x480ab000	/* on-the-go usb */
+
+#define PHYSINTC	0x48200000	/* interrupt controller */
+
+#define PHYSPRMIVA2	0x48206000	/* prm iva2 regs */
+/* 48306d40 sys_clkin_sel */
+#define PHYSPRMGLBL	0x48307200	/* prm global regs */
+#define PHYSPRMWKUSB	0x48307400
+
+#define PHYSCNTRL	0x4830a200	/* SoC id, etc. */
+#define PHYSWDT1	0x4830c000	/* wdt1, not on GP omaps */
+#define PHYSWDOG	0x48314000	/* watchdog timer, wdt2 */
+#define PHYSWDT2	0x48314000	/* watchdog timer, wdt2 */
+#define PHYSTIMER1	0x48318000
+
+#define PHYSL4WKUP	0x48328100	/* l4 wkup */
+#define PHYSL4PER	0x49000100	/* l4 per */
+
+#define PHYSCONS	0x49020000	/* uart console (third one) */
+
+#define PHYSWDT3	0x49030000	/* wdt3 */
+#define PHYSTIMER2	0x49032000
+#define PHYSTIMER3	0x49034000
+#define PHYSGPIO5	0x49056000
+#define PHYSGPIO6	0x49058000	/* contains igep ether gpio */
+
+#define PHYSIOEND	0x49100000	/* end of PHYSIO identity map */
+
+#define PHYSL4EMU	0x54006100	/* l4 emu */
+#define PHYSL4PROT	0x54728000	/* l4 protection regs */
+
+#define PHYSL3		0x68000000	/* l3 interconnect control */
+#define PHYSL3USB	0x68004000	/* l3 regs for usb */
+#define PHYSL3USBOTG	0x68004400	/* l3 regs for usb otg */
+
+#define PHYSPMRT	0x68010000	/* l3 PM register target */
+#define PHYSSMS		0x6c000000	/* cfg regs: sms addr space 2 */
+#define PHYSDRC		0x6d000000	/* sdram ctlr, addr space 3 */
+#define PHYSGPMC	0x6e000000	/* (non-dram) memory ctlr */
+
+#define PHYSDRAM	0x80000000
+
+#define PHYSNAND	0xf9000000
+
+#define VIRTIO		PHYSIO

+ 133 - 0
sys/src/9/omap/mkfile

@@ -0,0 +1,133 @@
+CONF=beagle
+CONFLIST=beagle
+
+# allegedly u-boot uses the bottom 3MB (up to 0x300000) so avoid that,
+# and leave 64K for plan9.ini.  loadaddr must match KTZERO in mem.h.
+# 0xc0310000 has worked, 0x80310000 should work but doesn't yet.
+loadaddr=0xc0310000
+
+objtype=arm
+</$objtype/mkfile
+p=9
+
+DEVS=`{rc ../port/mkdevlist $CONF}
+
+PORT=\
+	alarm.$O\
+	alloc.$O\
+	allocb.$O\
+	auth.$O\
+	cache.$O\
+	chan.$O\
+	dev.$O\
+	edf.$O\
+	fault.$O\
+	latin1.$O\
+	mul64fract.$O\
+	rebootcmd.$O\
+	page.$O\
+	parse.$O\
+	pgrp.$O\
+	portclock.$O\
+	print.$O\
+	proc.$O\
+	qio.$O\
+	qlock.$O\
+	segment.$O\
+	swap.$O\
+	sysfile.$O\
+	sysproc.$O\
+	taslock.$O\
+	tod.$O\
+	xalloc.$O\
+
+OBJ=\
+	l.$O\
+	lexception.$O\
+	lproc.$O\
+	arch.$O\
+	clock.$O\
+	fpi.$O\
+	fpiarm.$O\
+	fpimem.$O\
+	main.$O\
+	mmu.$O\
+	random.$O\
+	trap.$O\
+	$CONF.root.$O\
+	$CONF.rootc.$O\
+	$DEVS\
+	$PORT\
+
+LIB=\
+	/$objtype/lib/libmemlayer.a\
+	/$objtype/lib/libmemdraw.a\
+	/$objtype/lib/libdraw.a\
+	/$objtype/lib/libip.a\
+	/$objtype/lib/libsec.a\
+	/$objtype/lib/libmp.a\
+	/$objtype/lib/libc.a\
+
+9:V: $p$CONF s$p$CONF
+
+$p$CONF:DQ:	$CONF.c $OBJ $LIB mkfile
+	$CC $CFLAGS '-DKERNDATE='`{date -n} $CONF.c
+	echo '# linking raw kernel'
+	$LD -o $target -H0 -R4096 -T$loadaddr -l $OBJ $CONF.$O $LIB
+
+s$p$CONF:DQ:	$CONF.$O $OBJ $LIB
+	echo '# linking kernel with symbols'
+#	$LD -o $target -R4096 -T$loadaddr -l -a $OBJ $CONF.$O $LIB >$target.list
+	$LD -o $target -R4096 -T$loadaddr -l $OBJ $CONF.$O $LIB
+	size $target
+
+$p$CONF.gz:D:	$p$CONF
+	gzip -9 <$p$CONF >$target
+
+$OBJ: $HFILES
+
+install:V: /$objtype/$p$CONF
+
+/$objtype/$p$CONF:D: $p$CONF s$p$CONF
+	cp -x $p$CONF s$p$CONF /$objtype &
+	wait
+	touch $target
+
+<../boot/bootmkfile
+<../port/portmkfile
+<|../port/mkbootrules $CONF
+
+CFLAGS= -I. -I../port $CFLAGS	# hack to compile private sysproc.c (e.g.)
+
+arch.$O clock.$O fpiarm.$O main.$O mmu.$O screen.$O sdscsi.$O syscall.$O \
+	trap.$O: /$objtype/include/ureg.h
+
+archomap.$O devether.$0 ether9221.$O: etherif.h ../port/netif.h
+fpi.$O fpiarm.$O fpimem.$O: fpi.h
+l.$O lexception.$O lproc.$O mmu.$O: arm.s arm.h mem.h
+l.$O rebootcode.$O: cache.v7.s
+main.$O:	errstr.h init.h reboot.h
+mouse.$O:	screen.h
+devusb.$O:	usb.h
+usbehci.$O usbohci.$O usbuhci.$O: usb.h usbehci.h uncached.h
+
+init.h:D:	../port/initcode.c init9.s
+	$CC ../port/initcode.c
+	$AS init9.s
+	$LD -l -R1 -s -o init.out init9.$O initcode.$O /$objtype/lib/libc.a
+	{echo 'uchar initcode[]={'
+	 xd -1x <init.out |
+		sed -e 's/^[0-9a-f]+ //' -e 's/ ([0-9a-f][0-9a-f])/0x\1,/g'
+	 echo '};'} > init.h
+
+reboot.h:D:	rebootcode.s cache.v7.s arm.s arm.h mem.h
+	$AS rebootcode.s
+	# -lc is only for memmove.  -T arg is PADDR(REBOOTADDR)
+#	$LD -l -a -s -T0x100 -R4 -o reboot.out rebootcode.$O -lc >reboot.list
+	$LD -l -s -T0x100 -R4 -o reboot.out rebootcode.$O -lc
+	{echo 'uchar rebootcode[]={'
+	 xd -1x reboot.out |
+		sed -e '1,2d' -e 's/^[0-9a-f]+ //' -e 's/ ([0-9a-f][0-9a-f])/0x\1,/g'
+	 echo '};'} > reboot.h
+errstr.h:D:	../port/mkerrstr ../port/error.h
+	rc ../port/mkerrstr > errstr.h

+ 470 - 0
sys/src/9/omap/mmu.c

@@ -0,0 +1,470 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+#include "arm.h"
+
+#define L1X(va)		FEXT((va), 20, 12)
+#define L2X(va)		FEXT((va), 12, 8)
+
+enum {
+	L1lo		= UZERO/MiB,		/* L1X(UZERO)? */
+	L1hi		= (USTKTOP+MiB-1)/MiB,	/* L1X(USTKTOP+MiB-1)? */
+};
+
+#define ISHOLE(pte)	((pte) == 0)
+
+/* dump level 1 page table at virtual addr l1 */
+void
+mmudump(PTE *l1)
+{
+	int i, type, rngtype;
+	uintptr pa, startva, startpa;
+	uvlong va, endva;
+	PTE pte;
+
+//	pa -= MACHSIZE+1024;	/* put level 2 entries below level 1 */
+//	l2 = KADDR(pa);
+
+	print("\n");
+	endva = startva = startpa = 0;
+	rngtype = 0;
+	/* dump first level of ptes */
+	for (va = i = 0; i < 4096; i++) {
+		pte = l1[i];
+		pa = pte & ~(MB - 1);
+		type = pte & (Fine|Section|Coarse);
+		if (ISHOLE(pte)) {
+			if (endva != 0) {	/* open range? close it */
+				print("l1 maps va (%#lux-%#llux) -> pa %#lux type %#ux\n",
+					startva, endva-1, startpa, rngtype);
+				endva = 0;
+			}
+		} else {
+			if (endva == 0) {	/* no open range? start one */
+				startva = va;
+				startpa = pa;
+				rngtype = type;
+			}
+			endva = va + MB;	/* continue the open range */
+//			if (type == Coarse) {
+//				// could dump the l2 table for this l1 entry
+//			}
+		}
+		va += MB;
+	}
+	if (endva != 0)			/* close an open range */
+		print("l1 maps va (%#lux-%#llux) -> pa %#lux type %#ux\n",
+			startva, endva-1, startpa, rngtype);
+}
+
+static void
+idmap(PTE *l1, ulong va)	/* identity map the megabyte starting at va */
+{
+	l1[L1X(va)] = va | Dom0 | L1AP(Krw) | Section;
+}
+
+void
+mmuinit(void)
+{
+	uintptr pa;
+	PTE *l1, *l2;
+
+	pa = ttbget();
+	l1 = KADDR(pa);
+
+	/* redundant with l.s; only covers first MB of 17MB */
+	l1[L1X(VIRTIO)] = PHYSIO|Dom0|L1AP(Krw)|Section;
+
+	idmap(l1, PHYSETHER);		/* igep 9221 ethernet regs */
+	idmap(l1, PHYSL4PROT);
+	idmap(l1, PHYSL3);
+	idmap(l1, PHYSSMS);
+	idmap(l1, PHYSDRC);
+	idmap(l1, PHYSGPMC);
+
+	/* map high vectors to start of dram, but only 4K, not 1MB */
+	pa -= MACHSIZE+2*1024;
+	l2 = KADDR(pa);
+	memset(l2, 0, 1024);
+	/* vectors step on u-boot, but so do page tables */
+	l2[L2X(HVECTORS)] = PHYSDRAM|L2AP(Krw)|Small;
+	l1[L1X(HVECTORS)] = pa|Dom0|Coarse;	/* vectors -> ttb-machsize-2k */
+	coherence();
+
+	mmuinvalidate();
+	cacheuwbinv();
+	l2cacheuwbinv();
+
+	m->mmul1 = l1;
+//	mmudump(l1);			/* DEBUG */
+}
+
+static void
+mmul2empty(Proc* proc, int clear)
+{
+	PTE *l1;
+	Page **l2, *page;
+
+	l1 = m->mmul1;
+	l2 = &proc->mmul2;
+	for(page = *l2; page != nil; page = page->next){
+		if(clear)
+			memset(UINT2PTR(page->va), 0, BY2PG);
+		l1[page->daddr] = Fault;
+		l2 = &page->next;
+	}
+	*l2 = proc->mmul2cache;
+	proc->mmul2cache = proc->mmul2;
+	proc->mmul2 = nil;
+}
+
+static void
+mmul1empty(void)
+{
+#ifdef notdef
+/* there's a bug in here */
+	PTE *l1;
+
+	/* clean out any user mappings still in l1 */
+	if(m->mmul1lo > L1lo){
+		if(m->mmul1lo == 1)
+			m->mmul1[L1lo] = Fault;
+		else
+			memset(&m->mmul1[L1lo], 0, m->mmul1lo*sizeof(PTE));
+		m->mmul1lo = L1lo;
+	}
+	if(m->mmul1hi < L1hi){
+		l1 = &m->mmul1[m->mmul1hi];
+		if((L1hi - m->mmul1hi) == 1)
+			*l1 = Fault;
+		else
+			memset(l1, 0, (L1hi - m->mmul1hi)*sizeof(PTE));
+		m->mmul1hi = L1hi;
+	}
+#else
+	memset(&m->mmul1[L1lo], 0, (L1hi - L1lo)*sizeof(PTE));
+#endif /* notdef */
+}
+
+void
+mmuswitch(Proc* proc)
+{
+	int x;
+	PTE *l1;
+	Page *page;
+
+	/* do kprocs get here and if so, do they need to? */
+	if(m->mmupid == proc->pid && !proc->newtlb)
+		return;
+	m->mmupid = proc->pid;
+
+	/* write back dirty and invalidate l1 caches */
+	cacheuwbinv();
+
+	if(proc->newtlb){
+		mmul2empty(proc, 1);
+		proc->newtlb = 0;
+	}
+
+	mmul1empty();
+
+	/* move in new map */
+	l1 = m->mmul1;
+	for(page = proc->mmul2; page != nil; page = page->next){
+		x = page->daddr;
+		l1[x] = PPN(page->pa)|Dom0|Coarse;
+		/* know here that L1lo < x < L1hi */
+		if(x+1 - m->mmul1lo < m->mmul1hi - x)
+			m->mmul1lo = x+1;
+		else
+			m->mmul1hi = x;
+	}
+
+	/* make sure map is in memory */
+	/* could be smarter about how much? */
+	cachedwbse(&l1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE));
+
+	/* lose any possible stale tlb entries */
+	mmuinvalidate();
+
+	//print("mmuswitch l1lo %d l1hi %d %d\n",
+	//	m->mmul1lo, m->mmul1hi, proc->kp);
+}
+
+void
+flushmmu(void)
+{
+	int s;
+
+	s = splhi();
+	up->newtlb = 1;
+	mmuswitch(up);
+	splx(s);
+}
+
+void
+mmurelease(Proc* proc)
+{
+	Page *page, *next;
+
+	/* write back dirty and invalidate l1 caches */
+	cacheuwbinv();
+
+	mmul2empty(proc, 0);
+	for(page = proc->mmul2cache; page != nil; page = next){
+		next = page->next;
+		if(--page->ref)
+			panic("mmurelease: page->ref %d", page->ref);
+		pagechainhead(page);
+	}
+	if(proc->mmul2cache && palloc.r.p)
+		wakeup(&palloc.r);
+	proc->mmul2cache = nil;
+
+	mmul1empty();
+
+	/* make sure map is in memory */
+	/* could be smarter about how much? */
+	cachedwbse(&m->mmul1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE));
+
+	/* lose any possible stale tlb entries */
+	mmuinvalidate();
+}
+
+void
+putmmu(uintptr va, uintptr pa, Page* page)
+{
+	int x;
+	Page *pg;
+	PTE *l1, *pte;
+
+	x = L1X(va);
+	l1 = &m->mmul1[x];
+	//print("putmmu(%#p, %#p, %#p) ", va, pa, page->pa);
+	//print("mmul1 %#p l1 %#p *l1 %#ux x %d pid %d\n",
+	//	m->mmul1, l1, *l1, x, up->pid);
+	if(*l1 == Fault){
+		/* wasteful - l2 pages only have 256 entries - fix */
+		if(up->mmul2cache == nil){
+			/* auxpg since we don't need much? memset if so */
+			pg = newpage(1, 0, 0);
+			pg->va = VA(kmap(pg));
+		}
+		else{
+			pg = up->mmul2cache;
+			up->mmul2cache = pg->next;
+			memset(UINT2PTR(pg->va), 0, BY2PG);
+		}
+		pg->daddr = x;
+		pg->next = up->mmul2;
+		up->mmul2 = pg;
+
+		/* force l2 page to memory */
+		cachedwbse((void *)pg->va, BY2PG);
+
+		*l1 = PPN(pg->pa)|Dom0|Coarse;
+		cachedwbse(l1, sizeof *l1);
+		//print("l1 %#p *l1 %#ux x %d pid %d\n", l1, *l1, x, up->pid);
+
+		if(x >= m->mmul1lo && x < m->mmul1hi){
+			if(x+1 - m->mmul1lo < m->mmul1hi - x)
+				m->mmul1lo = x+1;
+			else
+				m->mmul1hi = x;
+		}
+	}
+	pte = UINT2PTR(KADDR(PPN(*l1)));
+	//print("pte %#p index %ld was %#ux\n", pte, L2X(va), *(pte+L2X(va)));
+
+	/* protection bits are
+	 *	PTERONLY|PTEVALID;
+	 *	PTEWRITE|PTEVALID;
+	 *	PTEWRITE|PTEUNCACHED|PTEVALID;
+	 */
+	x = Small;
+	if(!(pa & PTEUNCACHED))
+		x |= Cached|Buffered;
+	if(pa & PTEWRITE)
+		x |= L2AP(Urw);
+	else
+		x |= L2AP(Uro);
+	pte[L2X(va)] = PPN(pa)|x;
+	cachedwbse(&pte[L2X(va)], sizeof pte[0]);
+
+	/* clear out the current entry */
+	mmuinvalidateaddr(PPN(va));
+
+	/*  write back dirty entries - we need this because the pio() in
+	 *  fault.c is writing via a different virt addr and won't clean
+	 *  its changes out of the dcache.  Page coloring doesn't work
+	 *  on this mmu because the virtual cache is set associative
+	 *  rather than direct mapped.
+	 */
+	cachedwbinv();
+	if(page->cachectl[0] == PG_TXTFLUSH){
+		/* pio() sets PG_TXTFLUSH whenever a text pg has been written */
+		cacheiinv();
+		page->cachectl[0] = PG_NOFLUSH;
+	}
+	//print("putmmu %#p %#p %#p\n", va, pa, PPN(pa)|x);
+}
+
+void*
+mmuuncache(void* v, usize size)
+{
+	int x;
+	PTE *pte;
+	uintptr va;
+
+	/*
+	 * Simple helper for ucalloc().
+	 * Uncache a Section, must already be
+	 * valid in the MMU.
+	 */
+	va = PTR2UINT(v);
+	assert(!(va & (1*MiB-1)) && size == 1*MiB);
+
+	x = L1X(va);
+	pte = &m->mmul1[x];
+	if((*pte & (Fine|Section|Coarse)) != Section)
+		return nil;
+	*pte &= ~(Cached|Buffered);
+	mmuinvalidateaddr(va);
+	cachedwbinvse(pte, 4);
+
+	return v;
+}
+
+uintptr
+mmukmap(uintptr va, uintptr pa, usize size)
+{
+	int x;
+	PTE *pte;
+
+	/*
+	 * Stub.
+	 */
+	assert(!(va & (1*MiB-1)) && !(pa & (1*MiB-1)) && size == 1*MiB);
+
+	x = L1X(va);
+	pte = &m->mmul1[x];
+	if(*pte != Fault)
+		return 0;
+	*pte = pa|Dom0|L1AP(Krw)|Section;
+	mmuinvalidateaddr(va);
+	cachedwbinvse(pte, 4);
+
+	return va;
+}
+
+uintptr
+mmukunmap(uintptr va, uintptr pa, usize size)
+{
+	int x;
+	PTE *pte;
+
+	/*
+	 * Stub.
+	 */
+	assert(!(va & (1*MiB-1)) && !(pa & (1*MiB-1)) && size == 1*MiB);
+
+	x = L1X(va);
+	pte = &m->mmul1[x];
+	if(*pte != (pa|Dom0|L1AP(Krw)|Section))
+		return 0;
+	*pte = Fault;
+	mmuinvalidateaddr(va);
+	cachedwbinvse(pte, 4);
+
+	return va;
+}
+
+/*
+ * Return the number of bytes that can be accessed via KADDR(pa).
+ * If pa is not a valid argument to KADDR, return 0.
+ */
+uintptr
+cankaddr(uintptr pa)
+{
+	if(pa >= PHYSDRAM && pa < PHYSDRAM+memsize)
+		return PHYSDRAM+memsize - pa;
+	return 0;
+}
+
+/* from 386 */
+void*
+vmap(uintptr pa, usize size)
+{
+	uintptr pae, va;
+	usize o, osize;
+
+	/*
+	 * XXX - replace with new vm stuff.
+	 * Crock after crock - the first 4MB is mapped with 2MB pages
+	 * so catch that and return good values because the current mmukmap
+	 * will fail.
+	 */
+	if(pa+size < 4*MiB)
+		return UINT2PTR(kseg0|pa);
+
+	osize = size;
+	o = pa & (BY2PG-1);
+	pa -= o;
+	size += o;
+	size = ROUNDUP(size, PGSIZE);
+
+	va = kseg0|pa;
+	pae = mmukmap(va, pa, size);
+	if(pae == 0 || pae-size != pa)
+		panic("vmap(%#p, %ld) called from %#p: mmukmap fails %#p",
+			pa+o, osize, getcallerpc(&pa), pae);
+
+	return UINT2PTR(va+o);
+}
+
+/* from 386 */
+void
+vunmap(void* v, usize size)
+{
+	/*
+	 * XXX - replace with new vm stuff.
+	 * Can't do this until do real vmap for all space that
+	 * might be used, e.g. stuff below 1MB which is currently
+	 * mapped automagically at boot but that isn't used (or
+	 * at least shouldn't be used) by the kernel.
+	upafree(PADDR(v), size);
+	 */
+	USED(v, size);
+}
+
+/*
+ * Notes.
+ * Everything is in domain 0;
+ * domain 0 access bits in the DAC register are set
+ * to Client, which means access is controlled by the
+ * permission values set in the PTE.
+ *
+ * L1 access control for the kernel is set to 1 (RW,
+ * no user mode access);
+ * L2 access control for the kernel is set to 1 (ditto)
+ * for all 4 AP sets;
+ * L1 user mode access is never set;
+ * L2 access control for user mode is set to either
+ * 2 (RO) or 3 (RW) depending on whether text or data,
+ * for all 4 AP sets.
+ * (To get kernel RO set AP to 0 and S bit in control
+ * register c1).
+ * Coarse L1 page-tables are used. They have 256 entries
+ * and so consume 1024 bytes per table.
+ * Small L2 page-tables are used. They have 1024 entries
+ * and so consume 4096 bytes per table.
+ *
+ * 4KiB. That's the size of 1) a page, 2) the
+ * size allocated for an L2 page-table page (note only 1KiB
+ * is needed per L2 page - to be dealt with later) and
+ * 3) the size of the area in L1 needed to hold the PTEs
+ * to map 1GiB of user space (0 -> 0x3fffffff, 1024 entries).
+ */

+ 209 - 0
sys/src/9/omap/rebootcode.s

@@ -0,0 +1,209 @@
+/*
+ * omap3530 reboot code
+ *
+ * must fit in 11K to avoid stepping on PTEs; see mem.h.
+ *
+ * R11 is used by the loader as a temporary, so avoid it.
+ */
+#include "arm.s"
+
+/*
+ * Turn off MMU, then copy the new kernel to its correct location
+ * in physical memory.  Then jump to the start of the kernel.
+ */
+
+/* main(PADDR(entry), PADDR(code), size); */
+TEXT	main(SB), 1, $-4
+	MOVW	$setR12(SB), R12
+
+	MOVW	R0, p1+0(FP)		/* destination, passed in R0 */
+
+	MOVW	CPSR, R0
+	ORR	$(PsrDirq|PsrDfiq), R0
+	MOVW	R0, CPSR		/* splhi */
+	BARRIERS
+
+WAVE('R')
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	BIC	$CpACasa, R1	/* no speculative I access forwarding to mem */
+	/* slow down */
+	ORR	$(CpACcachenopipe|CpACcp15serial|CpACcp15waitidle|CpACcp15pipeflush), R1
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	BARRIERS
+
+	BL	cachesoff(SB)
+	/* now back in 29- or 26-bit addressing, mainly for SB */
+	/* double mapping of PHYSDRAM & KZERO now in effect */
+
+	/*
+	 * turn the MMU off
+	 */
+
+WAVE('e')
+	/* first switch to PHYSDRAM-based addresses */
+	DMB
+
+	MOVW	$KSEGM, R7		/* clear segment bits */
+	MOVW	$PHYSDRAM, R0		/* set dram base bits */
+	BIC	R7, R12			/* adjust SB */
+	ORR	R0, R12
+
+	BL	_r15warp(SB)
+	/* don't care about saving R14; we're not returning */
+
+	/*
+	 * now running in PHYSDRAM segment, not KZERO.
+	 */
+
+WAVE('b')
+	SUB	$12, SP				/* paranoia */
+	BL	cacheuwbinv(SB)
+	ADD	$12, SP				/* paranoia */
+
+	/* invalidate mmu mappings */
+	MOVW	$KZERO, R0			/* some valid virtual address */
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+
+WAVE('o')
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0)
+	BIC	$(CpCmmu|CpCdcache|CpCicache), R0
+	MCR     CpSC, 0, R0, C(CpCONTROL), C(0)	/* mmu off */
+	BARRIERS
+
+WAVE('o')
+	/* copy in arguments from stack frame before moving stack */
+	MOVW	p2+4(FP), R4		/* phys source */
+	MOVW	n+8(FP), R5		/* byte count */
+	MOVW	p1+0(FP), R6		/* phys destination */
+
+	/* set up a new stack for local vars and memmove args */
+	MOVW	R6, SP			/* tiny trampoline stack */
+	SUB	$(0x20 + 4), SP		/* back up before a.out header */
+
+//	MOVW	R14, -48(SP)		/* store return addr */
+	SUB	$48, SP			/* allocate stack frame */
+
+	MOVW	R5, 40(SP)		/* save count */
+	MOVW	R6, 44(SP)		/* save dest/entry */
+
+	DELAY(printloop2, 2)
+WAVE('t')
+
+	MOVW	40(SP), R5		/* restore count */
+	MOVW	44(SP), R6		/* restore dest/entry */
+	MOVW	R6, 0(SP)		/* normally saved LR goes here */
+	MOVW	R6, 4(SP)		/* push dest */
+	MOVW	R6, R0
+	MOVW	R4, 8(SP)		/* push src */
+	MOVW	R5, 12(SP)		/* push size */
+	BL	memmove(SB)
+
+WAVE('-')
+	/*
+	 * flush caches
+	 */
+	BL	cacheuwbinv(SB)
+
+WAVE('>')
+	DELAY(printloopret, 1)
+WAVE('\r')
+	DELAY(printloopnl, 1)
+WAVE('\n')
+/*
+ * jump to kernel entry point.  Note the true kernel entry point is
+ * the virtual address KZERO|R6, but this must wait until
+ * the MMU is enabled by the kernel in l.s
+ */
+	MOVW	44(SP), R6		/* restore R6 (dest/entry) */
+	ORR	R6, R6			/* NOP: avoid link bug */
+	B	(R6)
+WAVE('?')
+	B	0(PC)
+
+/*
+ * turn the caches off, double map PHYSDRAM & KZERO, invalidate TLBs, revert
+ * to tiny addresses.  upon return, it will be safe to turn off the mmu.
+ */
+TEXT cachesoff(SB), 1, $-4
+	MOVM.DB.W [R14,R1-R10], (R13)		/* save regs on stack */
+	MOVW	CPSR, R0
+	ORR	$(PsrDirq|PsrDfiq), R0
+	MOVW	R0, CPSR
+	BARRIERS
+
+	SUB	$12, SP				/* paranoia */
+	BL	cacheuwbinv(SB)
+	ADD	$12, SP				/* paranoia */
+
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0)
+	BIC	$(CpCicache|CpCdcache), R0
+	MCR     CpSC, 0, R0, C(CpCONTROL), C(0)	/* caches off */
+	BARRIERS
+
+	/*
+	 * caches are off
+	 */
+
+	/* invalidate stale TLBs before changing them */
+	MOVW	$KZERO, R0			/* some valid virtual address */
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+
+	/* redo double map of PHYSDRAM, KZERO */
+	MOVW	$PHYSDRAM, R3
+	CMP	$KZERO, R3
+	BEQ	noun2map
+	MOVW	$(L1+L1X(PHYSDRAM)), R4		/* address of PHYSDRAM's PTE */
+	MOVW	$PTEDRAM, R2			/* PTE bits */
+	MOVW	$DOUBLEMAPMBS, R5
+_ptrdbl:
+	ORR	R3, R2, R1		/* first identity-map 0 to 0, etc. */
+	MOVW	R1, (R4)
+	ADD	$4, R4				/* bump PTE address */
+	ADD	$MiB, R3			/* bump pa */
+	SUB.S	$1, R5
+	BNE	_ptrdbl
+noun2map:
+
+	/*
+	 * flush stale TLB entries
+	 */
+
+	BARRIERS
+	MOVW	$KZERO, R0			/* some valid virtual address */
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+
+	/* switch back to PHYSDRAM addressing, mainly for SB */
+	MOVW	$KSEGM, R7		/* clear segment bits */
+	MOVW	$PHYSDRAM, R0		/* set dram base bits */
+	BIC	R7, R12			/* adjust SB */
+	ORR	R0, R12
+	BIC	R7, SP
+	ORR	R0, SP
+
+	MOVM.IA.W (R13), [R14,R1-R10]		/* restore regs from stack */
+
+	MOVW	$KSEGM, R0		/* clear segment bits */
+	BIC	R0, R14			/* adjust link */
+	MOVW	$PHYSDRAM, R0		/* set dram base bits */
+	ORR	R0, R14
+
+	RET
+
+TEXT _r15warp(SB), 1, $-4
+	BIC	R7, R14			/* link */
+	ORR	R0, R14
+
+	BIC	R7, R13			/* SP */
+	ORR	R0, R13
+	RET
+
+TEXT panic(SB), 1, $-4		/* stub */
+WAVE('?')
+	RET
+TEXT pczeroseg(SB), 1, $-4	/* stub */
+	RET
+
+#include "cache.v7.s"

+ 740 - 0
sys/src/9/omap/trap.c

@@ -0,0 +1,740 @@
+/*
+ * omap3530 traps, exceptions, interrupts, system calls.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+#include "ureg.h"
+#include "arm.h"
+
+enum {
+	Nirqs = 96,
+	Nvec = 8,		/* # of vectors at start of lexception.s */
+	Bi2long = BI2BY * sizeof(long),
+};
+
+extern int notify(Ureg*);
+
+extern int ldrexvalid;
+
+/* omap35x intc (aka mpu_intc) */
+typedef struct Intrregs Intrregs;
+struct Intrregs {
+	/*
+	 * the manual inserts "INTCPS_" before each register name;
+	 * we'll just assume the prefix.
+	 */
+	uchar	_pad0[4*4];
+	ulong	sysconfig;
+	ulong	sysstatus;		/* ro */
+	uchar	_pad1[0x40 - 0x18];
+	ulong	sir_irq;		/* ro */
+	ulong	sir_fiq;		/* ro */
+	ulong	control;
+	ulong	protection;
+	ulong	idle;
+	uchar	_pad2[0x60 - 0x54];
+	ulong	irq_priority;
+	ulong	fiq_priority;
+	ulong	threshold;
+	uchar	_pad3[0x80 - 0x6c];
+	struct Bits {			/* bitmaps */
+		ulong	itr;		/* ro: pending intrs (no mask) */
+		ulong	mir;		/* interrupt mask: 1 means masked */
+		ulong	mir_clear;	/* wo: 1 sets the bit */
+		ulong	mir_set;	/* wo: 1 clears the bit */
+		ulong	isr_set;	/* software interrupts */
+		ulong	isr_clear;	/* wo */
+		ulong	pending_irq;	/* ro */
+		ulong	pending_fiq;	/* ro */
+	} bits[3];			/* 3*32 = 96 (Nirqs) */
+	ulong	ilr[Nirqs];
+};
+
+enum {
+	/* sysconfig bits */
+	Softreset	= 1<<1,
+
+	/* sysstatus bits */
+	Resetdone	= 1<<0,
+
+	/* sir_irq/fiq bits */
+	Activeirq	= MASK(7),
+
+	/* control bits */
+	Newirqagr	= 1<<0,
+
+	/* protection bits */
+	Protection	= 1<<0,
+
+	/* irq/fiq_priority bits */
+	Irqpriority	= MASK(6),
+
+	/* threshold bits */
+	Prioritythreshold = MASK(8),
+
+	/* ilr bits */
+	Priority	= MASK(8) - MASK(2),
+};
+
+typedef struct Vctl Vctl;
+typedef struct Vctl {
+	Vctl*	next;		/* handlers on this vector */
+	char	*name;		/* of driver, xallocated */
+	void	(*f)(Ureg*, void*);	/* handler to call */
+	void*	a;		/* argument to call it with */
+} Vctl;
+
+static Lock vctllock;
+static Vctl* vctl[Nirqs];
+
+/*
+ *   Layout at virtual address 0.
+ */
+typedef struct Vpage0 {
+	void	(*vectors[Nvec])(void);
+	u32int	vtable[Nvec];
+} Vpage0;
+static Vpage0 *vpage0;
+
+uvlong ninterrupt;
+uvlong ninterruptticks;
+int irqtooearly = 1;
+
+static volatile int probing, trapped;
+
+static int
+irqinuse(uint irq)
+{
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	/*
+	 * mir registers are odd: a 0 bit means intr unmasked (i.e.,
+	 * we've unmasked it because it's in use).
+	 */
+	return (ip->bits[irq / Bi2long].mir & (1 << (irq % Bi2long))) == 0;
+}
+
+static void
+intcmask(uint irq)
+{
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	ip->bits[irq / Bi2long].mir_set = 1 << (irq % Bi2long);
+	coherence();
+}
+
+static void
+intcunmask(uint irq)
+{
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	ip->bits[irq / Bi2long].mir_clear = 1 << (irq % Bi2long);
+	coherence();
+}
+
+static void
+intcmaskall(void)
+{
+	int i;
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	for (i = 0; i < 3; i++)
+		ip->bits[i].mir_set = ~0;
+	coherence();
+}
+
+static void
+intcunmaskall(void)
+{
+	int i;
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	for (i = 0; i < 3; i++)
+		ip->bits[i].mir_clear = ~0;
+	coherence();
+}
+
+static void
+intcinvertall(void)
+{
+	int i, s;
+	ulong bits;
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	s = splhi();
+	for (i = 0; i < 3; i++) {
+		bits = ip->bits[i].mir;
+		ip->bits[i].mir_set = ~0;	/* mask all */
+		coherence();
+		/* clearing enables only those intrs. that were disabled */
+		ip->bits[i].mir_clear = bits;
+	}
+	coherence();
+	splx(s);
+}
+
+static void
+intrsave(ulong buf[3])
+{
+	int i;
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	for (i = 0; i < nelem(buf); i++)
+		buf[i] = ip->bits[i].mir;
+	coherence();
+}
+
+static void
+intrrestore(ulong buf[3])
+{
+	int i, s;
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	s = splhi();
+	for (i = 0; i < nelem(buf); i++) {
+		ip->bits[i].mir_clear = ~0;	/* unmask all */
+		coherence();
+		ip->bits[i].mir_set = buf[i];	/* mask previously disabled */
+	}
+	coherence();
+	splx(s);
+}
+
+/*
+ *  set up for exceptions
+ */
+void
+trapinit(void)
+{
+	int i;
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	/* set up the exception vectors */
+	vpage0 = (Vpage0*)HVECTORS;
+	memmove(vpage0->vectors, vectors, sizeof(vpage0->vectors));
+	memmove(vpage0->vtable, vtable, sizeof(vpage0->vtable));
+	cacheuwbinv();
+	l2cacheuwbinv();
+
+	/* set up the stacks for the interrupt modes */
+	setr13(PsrMfiq, m->sfiq);
+	setr13(PsrMirq, m->sirq);
+	setr13(PsrMabt, m->sabt);
+	setr13(PsrMund, m->sund);
+#ifdef HIGH_SECURITY
+	setr13(PsrMmon, m->smon);
+#endif
+	setr13(PsrMsys, m->ssys);
+
+	intcmaskall();
+	ip->control = 0;
+	ip->threshold = Prioritythreshold;	/* disable threshold */
+	for (i = 0; i < Nirqs; i++)
+		ip->ilr[i] = 0<<2 | 0;	/* all intrs pri 0 & to irq, not fiq */
+	irqtooearly = 0;
+	coherence();
+}
+
+void
+intrsoff(void)
+{
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+
+	intcmaskall();
+	ip->control = Newirqagr;	/* dismiss interrupt */
+	coherence();
+}
+
+/*
+ *  enable an irq interrupt
+ */
+int
+irqenable(int irq, void (*f)(Ureg*, void*), void* a, char *name)
+{
+	Vctl *v;
+
+	if(irq >= nelem(vctl) || irq < 0)
+		panic("irqenable irq %d", irq);
+
+	if (irqtooearly) {
+		iprint("irqenable for %d %s called too early\n", irq, name);
+		return -1;
+	}
+	if(irqinuse(irq))
+		print("irqenable: %s: irq %d already in use, chaining\n",
+			name, irq);
+	v = malloc(sizeof(Vctl));
+	if (v == nil)
+		panic("irqenable: malloc Vctl");
+	v->f = f;
+	v->a = a;
+	v->name = malloc(strlen(name)+1);
+	if (v->name == nil)
+		panic("irqenable: malloc name");
+	strcpy(v->name, name);
+
+	lock(&vctllock);
+	v->next = vctl[irq];
+	vctl[irq] = v;
+
+	intcunmask(irq);
+	unlock(&vctllock);
+	return 0;
+}
+
+/*
+ *  disable an irq interrupt
+ */
+int
+irqdisable(int irq, void (*f)(Ureg*, void*), void* a, char *name)
+{
+	Vctl **vp, *v;
+
+	if(irq >= nelem(vctl) || irq < 0)
+		panic("irqdisable irq %d", irq);
+
+	lock(&vctllock);
+	for(vp = &vctl[irq]; v = *vp; vp = &v->next)
+		if (v->f == f && v->a == a && strcmp(v->name, name) == 0){
+			print("irqdisable: remove %s\n", name);
+			*vp = v->next;
+			free(v);
+			break;
+		}
+
+	if(v == nil)
+		print("irqdisable: irq %d, name %s not enabled\n", irq, name);
+	if(vctl[irq] == nil){
+		print("irqdisable: clear icmr bit %d\n", irq);
+		intcmask(irq);
+	}
+	unlock(&vctllock);
+
+	return 0;
+}
+
+/*
+ *  called by trap to handle access faults
+ */
+static void
+faultarm(Ureg *ureg, uintptr va, int user, int read)
+{
+	int n, insyscall;
+	char buf[ERRMAX];
+
+	if(up == nil) {
+		dumpregs(ureg);
+		panic("fault: nil up in faultarm, accessing %#p", va);
+	}
+	insyscall = up->insyscall;
+	up->insyscall = 1;
+	n = fault(va, read);
+	if(n < 0){
+		if(!user){
+			dumpregs(ureg);
+			panic("fault: kernel accessing %#p", va);
+		}
+		/* don't dump registers; programs suicide all the time */
+		snprint(buf, sizeof buf, "sys: trap: fault %s va=%#p",
+			read? "read": "write", va);
+		postnote(up, 1, buf, NDebug);
+	}
+	up->insyscall = insyscall;
+}
+
+/*
+ *  called by trap to handle interrupts.
+ *  returns true iff a clock interrupt, thus maybe reschedule.
+ */
+static int
+irq(Ureg* ureg)
+{
+	int clockintr;
+	uint irqno, handled, t, ticks = perfticks();
+	Intrregs *ip = (Intrregs *)PHYSINTC;
+	Vctl *v;
+	static int nesting, lastirq = -1;
+
+	handled = 0;
+	irqno = ip->sir_irq & Activeirq;
+
+	if (irqno >= 37 && irqno <= 47)		/* this is a clock intr? */
+		m->inclockintr++;		/* yes, count nesting */
+	lastirq = irqno;
+
+	if (irqno >= nelem(vctl)) {
+		iprint("trap: irq %d >= # vectors (%d)\n", irqno, nelem(vctl));
+		ip->control = Newirqagr;	/* dismiss interrupt */
+		return 0;
+	}
+
+	++nesting;
+	for(v = vctl[irqno]; v != nil; v = v->next)
+		if (v->f) {
+			if (islo())
+				panic("trap: pl0 before trap handler for %s",
+					v->name);
+			v->f(ureg, v->a);
+			if (islo())
+				panic("trap: %s lowered pl", v->name);
+//			splhi();		/* in case v->f lowered pl */
+			handled++;
+		}
+	if(!handled)
+		print("unknown interrupt: irq %d\n", irqno);
+	t = perfticks();
+	ninterrupt++;
+	if(t < ticks)
+		ninterruptticks += ticks-t;
+	else
+		ninterruptticks += t-ticks;
+	ip->control = Newirqagr;	/* dismiss interrupt */
+	coherence();
+
+	--nesting;
+	clockintr = m->inclockintr == 1;
+	if (irqno >= 37 && irqno <= 47)
+		m->inclockintr--;
+	return clockintr;
+}
+
+/*
+ *  returns 1 if the instruction writes memory, 0 otherwise
+ */
+int
+writetomem(ulong inst)
+{
+	/* swap always write memory */
+	if((inst & 0x0FC00000) == 0x01000000)
+		return 1;
+
+	/* loads and stores are distinguished by bit 20 */
+	if(inst & (1<<20))
+		return 0;
+
+	return 1;
+}
+
+/*
+ *  here on all exceptions other than syscall (SWI)
+ */
+void
+trap(Ureg *ureg)
+{
+	int clockintr, user, x, rv, rem;
+	ulong inst, fsr;
+	uintptr va;
+	char buf[ERRMAX];
+
+	splhi();			/* paranoia */
+	if(up != nil)
+		rem = ((char*)ureg)-up->kstack;
+	else
+		rem = ((char*)ureg)-((char*)m+sizeof(Mach));
+	if(rem < 1024) {
+		iprint("trap: %d stack bytes left, up %#p ureg %#p at pc %#lux\n",
+			rem, up, ureg, ureg->pc);
+		delay(1000);
+		dumpstack();
+		panic("trap: %d stack bytes left, up %#p ureg %#p at pc %#lux",
+			rem, up, ureg, ureg->pc);
+	}
+
+	user = (ureg->psr & PsrMask) == PsrMusr;
+	if(user){
+		up->dbgreg = ureg;
+		cycles(&up->kentry);
+	}
+
+	/*
+	 * All interrupts/exceptions should be resumed at ureg->pc-4,
+	 * except for Data Abort which resumes at ureg->pc-8.
+	 */
+	if(ureg->type == (PsrMabt+1))
+		ureg->pc -= 8;
+	else
+		ureg->pc -= 4;
+
+	clockintr = 0;		/* if set, may call sched() before return */
+	switch(ureg->type){
+	default:
+		panic("unknown trap; type %#lux, psr mode %#lux", ureg->type,
+			ureg->psr & PsrMask);
+		break;
+	case PsrMirq:
+		ldrexvalid = 0;
+		clockintr = irq(ureg);
+		break;
+	case PsrMabt:			/* prefetch fault */
+		ldrexvalid = 0;
+		faultarm(ureg, ureg->pc, user, 1);
+		break;
+	case PsrMabt+1:			/* data fault */
+		ldrexvalid = 0;
+		va = farget();
+		inst = *(ulong*)(ureg->pc);
+		fsr = fsrget() & 0xf;
+		if (probing && !user) {
+			if (trapped++ > 0)
+				panic("trap: recursive probe %#lux", va);
+			ureg->pc += 4;	/* continue at next instruction */
+			break;
+		}
+		switch(fsr){
+		case 0x0:
+			panic("vector exception at %#lux", ureg->pc);
+			break;
+		case 0x1:
+		case 0x3:
+			if(user){
+				snprint(buf, sizeof buf,
+					"sys: alignment: pc %#lux va %#p\n",
+					ureg->pc, va);
+				postnote(up, 1, buf, NDebug);
+			} else
+				panic("kernel alignment: pc %#lux va %#p", ureg->pc, va);
+			break;
+		case 0x2:
+			panic("terminal exception at %#lux", ureg->pc);
+			break;
+		case 0x4:
+		case 0x6:
+		case 0x8:
+		case 0xa:
+		case 0xc:
+		case 0xe:
+			panic("external abort %#lux pc %#lux addr %#p",
+				fsr, ureg->pc, va);
+			break;
+		case 0x5:		/* translation fault, no section entry */
+		case 0x7:		/* translation fault, no page entry */
+			faultarm(ureg, va, user, !writetomem(inst));
+			break;
+		case 0x9:
+		case 0xb:
+			/* domain fault, accessing something we shouldn't */
+			if(user){
+				snprint(buf, sizeof buf,
+					"sys: access violation: pc %#lux va %#p\n",
+					ureg->pc, va);
+				postnote(up, 1, buf, NDebug);
+			} else
+				panic("kernel access violation: pc %#lux va %#p",
+					ureg->pc, va);
+			break;
+		case 0xd:
+		case 0xf:
+			/* permission error, copy on write or real permission error */
+			faultarm(ureg, va, user, !writetomem(inst));
+			break;
+		}
+		break;
+	case PsrMund:			/* undefined instruction */
+		if(user){
+			/* look for floating point instructions to interpret */
+			x = spllo();
+			rv = fpiarm(ureg);
+			splx(x);
+			if(rv == 0){
+				ldrexvalid = 0;
+				snprint(buf, sizeof buf,
+					"undefined instruction: pc %#lux\n",
+					ureg->pc);
+				postnote(up, 1, buf, NDebug);
+			}
+		}else{
+			if (ureg->pc & 3) {
+				iprint("rounding fault pc %#lux down to word\n",
+					ureg->pc);
+				ureg->pc &= ~3;
+			}
+			iprint("undefined instruction: pc %#lux inst %#ux\n",
+				ureg->pc, ((u32int*)ureg->pc)[-2]);
+			panic("undefined instruction");
+		}
+		break;
+	}
+	splhi();
+
+	/* delaysched set because we held a lock or because our quantum ended */
+	if(up && up->delaysched && clockintr){
+		ldrexvalid = 0;
+		sched();		/* can cause more traps */
+		splhi();
+	}
+
+	if(user){
+		if(up->procctl || up->nnote)
+			notify(ureg);
+		kexit(ureg);
+	}
+}
+
+/*
+ * Fill in enough of Ureg to get a stack trace, and call a function.
+ * Used by debugging interface rdb.
+ */
+void
+callwithureg(void (*fn)(Ureg*))
+{
+	Ureg ureg;
+
+	ureg.pc = getcallerpc(&fn);
+	ureg.sp = PTR2UINT(&fn);
+	fn(&ureg);
+}
+
+static void
+dumpstackwithureg(Ureg *ureg)
+{
+	int x;
+	uintptr l, v, i, estack;
+	char *s;
+
+	dumpregs(ureg);
+	if((s = getconf("*nodumpstack")) != nil && strcmp(s, "0") != 0){
+		iprint("dumpstack disabled\n");
+		return;
+	}
+	iprint("dumpstack\n");
+
+	x = 0;
+	x += iprint("ktrace /kernel/path %#.8lux %#.8lux %#.8lux # pc, sp, link\n",
+		ureg->pc, ureg->sp, ureg->r14);
+	delay(20);
+	i = 0;
+	if(up
+	&& (uintptr)&l >= (uintptr)up->kstack
+	&& (uintptr)&l <= (uintptr)up->kstack+KSTACK)
+		estack = (uintptr)up->kstack+KSTACK;
+	else if((uintptr)&l >= (uintptr)m->stack
+	&& (uintptr)&l <= (uintptr)m+MACHSIZE)
+		estack = (uintptr)m+MACHSIZE;
+	else
+		return;
+	x += iprint("estackx %p\n", estack);
+
+	for(l = (uintptr)&l; l < estack; l += sizeof(uintptr)){
+		v = *(uintptr*)l;
+		if((KTZERO < v && v < (uintptr)etext) || estack-l < 32){
+			x += iprint("%.8p ", v);
+			delay(20);
+			i++;
+		}
+		if(i == 8){
+			i = 0;
+			x += iprint("\n");
+			delay(20);
+		}
+	}
+	if(i)
+		iprint("\n");
+}
+
+void
+dumpstack(void)
+{
+	callwithureg(dumpstackwithureg);
+}
+
+/*
+ * dump system control coprocessor registers
+ */
+static void
+dumpscr(void)
+{
+	iprint("0:\t%#8.8ux id\n", cpidget());
+	iprint("\t%8.8#ux ct\n", cpctget());
+	iprint("1:\t%#8.8ux control\n", controlget());
+	iprint("2:\t%#8.8ux ttb\n", ttbget());
+	iprint("3:\t%#8.8ux dac\n", dacget());
+	iprint("4:\t(reserved)\n");
+	iprint("5:\t%#8.8ux fsr\n", fsrget());
+	iprint("6:\t%#8.8ux far\n", farget());
+	iprint("7:\twrite-only cache\n");
+	iprint("8:\twrite-only tlb\n");
+	iprint("13:\t%#8.8ux pid\n", pidget());
+	delay(10);
+}
+
+/*
+ * dump general registers
+ */
+static void
+dumpgpr(Ureg* ureg)
+{
+	if(up != nil)
+		iprint("cpu%d: registers for %s %lud\n",
+			m->machno, up->text, up->pid);
+	else
+		iprint("cpu%d: registers for kernel\n", m->machno);
+
+	delay(20);
+	iprint("%#8.8lux\tr0\n", ureg->r0);
+	iprint("%#8.8lux\tr1\n", ureg->r1);
+	iprint("%#8.8lux\tr2\n", ureg->r2);
+	delay(20);
+	iprint("%#8.8lux\tr3\n", ureg->r3);
+	iprint("%#8.8lux\tr4\n", ureg->r4);
+	iprint("%#8.8lux\tr5\n", ureg->r5);
+	delay(20);
+	iprint("%#8.8lux\tr6\n", ureg->r6);
+	iprint("%#8.8lux\tr7\n", ureg->r7);
+	iprint("%#8.8lux\tr8\n", ureg->r8);
+	delay(20);
+	iprint("%#8.8lux\tr9 (up)\n", ureg->r9);
+	iprint("%#8.8lux\tr10 (m)\n", ureg->r10);
+	iprint("%#8.8lux\tr11 (loader temporary)\n", ureg->r11);
+	iprint("%#8.8lux\tr12 (SB)\n", ureg->r12);
+	delay(20);
+	iprint("%#8.8lux\tr13 (sp)\n", ureg->r13);
+	iprint("%#8.8lux\tr14 (link)\n", ureg->r14);
+	iprint("%#8.8lux\tr15 (pc)\n", ureg->pc);
+	delay(20);
+	iprint("%10.10lud\ttype\n", ureg->type);
+	iprint("%#8.8lux\tpsr\n", ureg->psr);
+	delay(20);
+}
+
+void
+dumpregs(Ureg* ureg)
+{
+	dumpgpr(ureg);
+	dumpscr();
+}
+
+void
+idlehands(void)
+{
+	extern void _idlehands(void);
+
+	_idlehands();
+}
+
+vlong
+probeaddr(uintptr addr)
+{
+	vlong v;
+	static Lock fltlck;
+
+	ilock(&fltlck);
+	trapped = 0;
+	probing = 1;
+	coherence();
+
+	v = *(ulong *)addr;	/* this may cause a fault */
+	USED(probing);
+	coherence();
+
+	probing = 0;
+	coherence();
+	if (trapped)
+		v = -1;
+	iunlock(&fltlck);
+	return v;
+}

+ 813 - 0
sys/src/9/omap/uarti8250.c

@@ -0,0 +1,813 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+/*
+ * we ignore the first 2 uarts on the omap3530 (see below) and use the
+ * third one but call it 0.
+ */
+
+/*
+ * 8250 UART and compatibles.
+ */
+enum {					/* registers */
+	Rbr		= 0,		/* Receiver Buffer (RO) */
+	Thr		= 0,		/* Transmitter Holding (WO) */
+	Ier		= 1,		/* Interrupt Enable */
+	Iir		= 2,		/* Interrupt Identification (RO) */
+	Fcr		= 2,		/* FIFO Control (WO) */
+	Lcr		= 3,		/* Line Control */
+	Mcr		= 4,		/* Modem Control */
+	Lsr		= 5,		/* Line Status */
+	Msr		= 6,		/* Modem Status */
+	Scr		= 7,		/* Scratch Pad */
+	Usr		= 31,		/* Uart Status Register */
+	Dll		= 0,		/* Divisor Latch LSB */
+	Dlm		= 1,		/* Divisor Latch MSB */
+};
+
+enum {					/* Usr */
+	Busy		= 0x01,
+};
+
+enum {					/* Ier */
+	Erda		= 0x01,		/* Enable Received Data Available */
+	Ethre		= 0x02,		/* Enable Thr Empty */
+	Erls		= 0x04,		/* Enable Receiver Line Status */
+	Ems		= 0x08,		/* Enable Modem Status */
+};
+
+enum {					/* Iir */
+	Ims		= 0x00,		/* Ms interrupt */
+	Ip		= 0x01,		/* Interrupt Pending (not) */
+	Ithre		= 0x02,		/* Thr Empty */
+	Irda		= 0x04,		/* Received Data Available */
+	Irls		= 0x06,		/* Receiver Line Status */
+	Ictoi		= 0x0C,		/* Character Time-out Indication */
+	IirMASK		= 0x3F,
+	Ifena		= 0xC0,		/* FIFOs enabled */
+};
+
+enum {					/* Fcr */
+	FIFOena		= 0x01,		/* FIFO enable */
+	FIFOrclr	= 0x02,		/* clear Rx FIFO */
+	FIFOtclr	= 0x04,		/* clear Tx FIFO */
+	FIFO1		= 0x00,		/* Rx FIFO trigger level 1 byte */
+	FIFO4		= 0x40,		/*	4 bytes */
+	FIFO8		= 0x80,		/*	8 bytes */
+	FIFO14		= 0xC0,		/*	14 bytes */
+};
+
+enum {					/* Lcr */
+	Wls5		= 0x00,		/* Word Length Select 5 bits/byte */
+	Wls6		= 0x01,		/*	6 bits/byte */
+	Wls7		= 0x02,		/*	7 bits/byte */
+	Wls8		= 0x03,		/*	8 bits/byte */
+	WlsMASK		= 0x03,
+	Stb		= 0x04,		/* 2 stop bits */
+	Pen		= 0x08,		/* Parity Enable */
+	Eps		= 0x10,		/* Even Parity Select */
+	Stp		= 0x20,		/* Stick Parity */
+	Brk		= 0x40,		/* Break */
+	Dlab		= 0x80,		/* Divisor Latch Access Bit */
+};
+
+enum {					/* Mcr */
+	Dtr		= 0x01,		/* Data Terminal Ready */
+	Rts		= 0x02,		/* Ready To Send */
+	Out1		= 0x04,		/* no longer in use */
+	Ie		= 0x08,		/* IRQ Enable */
+	Dm		= 0x10,		/* Diagnostic Mode loopback */
+};
+
+enum {					/* Lsr */
+	Dr		= 0x01,		/* Data Ready */
+	Oe		= 0x02,		/* Overrun Error */
+	Pe		= 0x04,		/* Parity Error */
+	Fe		= 0x08,		/* Framing Error */
+	Bi		= 0x10,		/* Break Interrupt */
+	Thre		= 0x20,		/* Thr Empty */
+	Temt		= 0x40,		/* Tramsmitter Empty */
+	FIFOerr		= 0x80,		/* error in receiver FIFO */
+};
+
+enum {					/* Msr */
+	Dcts		= 0x01,		/* Delta Cts */
+	Ddsr		= 0x02,		/* Delta Dsr */
+	Teri		= 0x04,		/* Trailing Edge of Ri */
+	Ddcd		= 0x08,		/* Delta Dcd */
+	Cts		= 0x10,		/* Clear To Send */
+	Dsr		= 0x20,		/* Data Set Ready */
+	Ri		= 0x40,		/* Ring Indicator */
+	Dcd		= 0x80,		/* Data Set Ready */
+};
+
+typedef struct Ctlr {
+	u32int*	io;
+	int	irq;
+	int	tbdf;
+	int	iena;
+	int	poll;
+
+	uchar	sticky[8];
+
+	Lock;
+	int	hasfifo;
+	int	checkfifo;
+	int	fena;
+} Ctlr;
+
+extern PhysUart i8250physuart;
+extern int normalprint;
+
+static Ctlr i8250ctlr[] = {
+{	.io	= (u32int*)PHYSCONS,
+	.irq	= 74,
+	.tbdf	= -1,
+	.poll	= 0, },
+/* these exist, but I don't think they're connected to anything external */
+//{	.io	= (u32int*)PHYSUART0,
+//	.irq	= 72,
+//	.tbdf	= -1,
+//	.poll	= 0, },
+//
+//{	.io	= (u32int*)PHYSUART1,
+//	.irq	= 73,
+//	.tbdf	= -1,
+//	.poll	= 0, },
+};
+
+static Uart i8250uart[] = {
+{	.regs	= &i8250ctlr[0], /* not [2] */
+	.name	= "COM3",
+	.freq	= 3686000,	/* Not used, we use the global i8250freq */
+	.phys	= &i8250physuart,
+	.console= 1,
+	.next	= nil, },
+
+/* these exist, but I don't think they're connected to anything external */
+//{	.regs	= &i8250ctlr[0],
+//	.name	= "COM1",
+//	.freq	= 3686000,	/* Not used, we use the global i8250freq */
+//	.phys	= &i8250physuart,
+//	.special= 0,
+//	.next	= &i8250uart[1], },
+//
+//{	.regs	= &i8250ctlr[1],
+//	.name	= "COM2",
+//	.freq	= 3686000,	/* Not used, we use the global i8250freq */
+//	.phys	= &i8250physuart,
+//	.special= 0,
+//	.next	= &i8250uart[2], },
+};
+
+#define csr8r(c, r)	((c)->io[(r)])
+#define csr8w(c, r, v)	((c)->io[(r)] = (c)->sticky[(r)]|(v))
+#define csr8o(c, r, v)	((c)->io[(r)] = (v))
+
+static long
+i8250status(Uart* uart, void* buf, long n, long offset)
+{
+	char *p;
+	Ctlr *ctlr;
+	uchar ier, lcr, mcr, msr;
+
+	ctlr = uart->regs;
+	p = malloc(READSTR);
+	mcr = ctlr->sticky[Mcr];
+	msr = csr8r(ctlr, Msr);
+	ier = ctlr->sticky[Ier];
+	lcr = ctlr->sticky[Lcr];
+	snprint(p, READSTR,
+		"b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n"
+		"dev(%d) type(%d) framing(%d) overruns(%d) "
+		"berr(%d) serr(%d)%s%s%s%s\n",
+
+		uart->baud,
+		uart->hup_dcd,
+		(msr & Dsr) != 0,
+		uart->hup_dsr,
+		(lcr & WlsMASK) + 5,
+		(ier & Ems) != 0,
+		(lcr & Pen) ? ((lcr & Eps) ? 'e': 'o'): 'n',
+		(mcr & Rts) != 0,
+		(lcr & Stb) ? 2: 1,
+		ctlr->fena,
+
+		uart->dev,
+		uart->type,
+		uart->ferr,
+		uart->oerr,
+		uart->berr,
+		uart->serr,
+		(msr & Cts) ? " cts": "",
+		(msr & Dsr) ? " dsr": "",
+		(msr & Dcd) ? " dcd": "",
+		(msr & Ri) ? " ring": ""
+	);
+	n = readstr(offset, buf, n, p);
+	free(p);
+
+	return n;
+}
+
+static void
+i8250fifo(Uart* uart, int level)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	if(ctlr->hasfifo == 0)
+		return;
+
+	/*
+	 * Changing the FIFOena bit in Fcr flushes data
+	 * from both receive and transmit FIFOs; there's
+	 * no easy way to guarantee not losing data on
+	 * the receive side, but it's possible to wait until
+	 * the transmitter is really empty.
+	 */
+	ilock(ctlr);
+	while(!(csr8r(ctlr, Lsr) & Temt))
+		;
+
+	/*
+	 * Set the trigger level, default is the max.
+	 * value.
+	 * Some UARTs require FIFOena to be set before
+	 * other bits can take effect, so set it twice.
+	 */
+	ctlr->fena = level;
+	switch(level){
+	case 0:
+		break;
+	case 1:
+		level = FIFO1|FIFOena;
+		break;
+	case 4:
+		level = FIFO4|FIFOena;
+		break;
+	case 8:
+		level = FIFO8|FIFOena;
+		break;
+	default:
+		level = FIFO14|FIFOena;
+		break;
+	}
+	csr8w(ctlr, Fcr, level);
+	csr8w(ctlr, Fcr, level);
+	iunlock(ctlr);
+}
+
+static void
+i8250dtr(Uart* uart, int on)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * Toggle DTR.
+	 */
+	ctlr = uart->regs;
+	if(on)
+		ctlr->sticky[Mcr] |= Dtr;
+	else
+		ctlr->sticky[Mcr] &= ~Dtr;
+	csr8w(ctlr, Mcr, 0);
+	coherence();
+}
+
+static void
+i8250rts(Uart* uart, int on)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * Toggle RTS.
+	 */
+	ctlr = uart->regs;
+	if(on)
+		ctlr->sticky[Mcr] |= Rts;
+	else
+		ctlr->sticky[Mcr] &= ~Rts;
+	csr8w(ctlr, Mcr, 0);
+	coherence();
+}
+
+static void
+i8250modemctl(Uart* uart, int on)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	ilock(&uart->tlock);
+	if(on){
+		ctlr->sticky[Ier] |= Ems;
+		csr8w(ctlr, Ier, ctlr->sticky[Ier]);
+		uart->modem = 1;
+		uart->cts = csr8r(ctlr, Msr) & Cts;
+	}
+	else{
+		ctlr->sticky[Ier] &= ~Ems;
+		csr8w(ctlr, Ier, ctlr->sticky[Ier]);
+		uart->modem = 0;
+		uart->cts = 1;
+	}
+	iunlock(&uart->tlock);
+
+	/* modem needs fifo */
+	(*uart->phys->fifo)(uart, on);
+}
+
+static int
+i8250parity(Uart* uart, int parity)
+{
+	int lcr;
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	lcr = ctlr->sticky[Lcr] & ~(Eps|Pen);
+
+	switch(parity){
+	case 'e':
+		lcr |= Eps|Pen;
+		break;
+	case 'o':
+		lcr |= Pen;
+		break;
+	case 'n':
+		break;
+	default:
+		return -1;
+	}
+	ctlr->sticky[Lcr] = lcr;
+	csr8w(ctlr, Lcr, 0);
+	coherence();
+
+	uart->parity = parity;
+
+	return 0;
+}
+
+static int
+i8250stop(Uart* uart, int stop)
+{
+	int lcr;
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	lcr = ctlr->sticky[Lcr] & ~Stb;
+
+	switch(stop){
+	case 1:
+		break;
+	case 2:
+		lcr |= Stb;
+		break;
+	default:
+		return -1;
+	}
+	ctlr->sticky[Lcr] = lcr;
+	csr8w(ctlr, Lcr, 0);
+	coherence();
+
+	uart->stop = stop;
+
+	return 0;
+}
+
+static int
+i8250bits(Uart* uart, int bits)
+{
+	int lcr;
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	lcr = ctlr->sticky[Lcr] & ~WlsMASK;
+
+	switch(bits){
+	case 5:
+		lcr |= Wls5;
+		break;
+	case 6:
+		lcr |= Wls6;
+		break;
+	case 7:
+		lcr |= Wls7;
+		break;
+	case 8:
+		lcr |= Wls8;
+		break;
+	default:
+		return -1;
+	}
+	ctlr->sticky[Lcr] = lcr;
+	csr8w(ctlr, Lcr, 0);
+	coherence();
+
+	uart->bits = bits;
+
+	return 0;
+}
+
+static int
+i8250baud(Uart* uart, int baud)
+{
+#ifdef notdef				/* don't change the speed */
+	ulong bgc;
+	Ctlr *ctlr;
+	extern int i8250freq;	/* In the config file */
+	/*
+	 * Set the Baud rate by calculating and setting the Baud rate
+	 * Generator Constant. This will work with fairly non-standard
+	 * Baud rates.
+	 */
+	if(i8250freq == 0 || baud <= 0)
+		return -1;
+	bgc = (i8250freq+8*baud-1)/(16*baud);
+
+	ctlr = uart->regs;
+	while(csr8r(ctlr, Usr) & Busy)
+		delay(1);
+	csr8w(ctlr, Lcr, Dlab);
+	csr8o(ctlr, Dlm, bgc>>8);
+	csr8o(ctlr, Dll, bgc);
+	csr8w(ctlr, Lcr, 0);
+	coherence();
+
+	uart->baud = baud;
+#else
+	USED(uart, baud);
+#endif
+	return 0;
+}
+
+static void
+i8250break(Uart* uart, int ms)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * Send a break.
+	 */
+	if(ms <= 0)
+		ms = 200;
+
+	ctlr = uart->regs;
+	csr8w(ctlr, Lcr, Brk);
+	coherence();
+	tsleep(&up->sleep, return0, 0, ms);
+	csr8w(ctlr, Lcr, 0);
+	coherence();
+}
+
+static void
+i8250kick(Uart* uart)
+{
+	int i;
+	Ctlr *ctlr;
+
+	if(/* uart->cts == 0 || */ uart->blocked)
+		return;
+
+	/* nothing more to send? then disable xmit intr */
+	ctlr = uart->regs;
+	if (uart->op >= uart->oe && csr8r(ctlr, Lsr) & Temt) {
+		ctlr->sticky[Ier] &= ~Ethre;
+		csr8w(ctlr, Ier, ctlr->sticky[Ier]);
+		coherence();
+		return;
+	}
+
+	/*
+	 *  128 here is an arbitrary limit to make sure
+	 *  we don't stay in this loop too long.  If the
+	 *  chip's output queue is longer than 128, too
+	 *  bad -- presotto
+	 */
+	for(i = 0; i < 128; i++){
+		if(!(csr8r(ctlr, Lsr) & Thre))
+			break;
+		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
+			break;
+		csr8o(ctlr, Thr, *(uart->op++));
+		coherence();
+		ctlr->sticky[Ier] |= Ethre;
+		csr8w(ctlr, Ier, ctlr->sticky[Ier]);
+		coherence();
+	}
+}
+
+static void
+i8250interrupt(Ureg*, void* arg)
+{
+	Ctlr *ctlr;
+	Uart *uart;
+	int iir, lsr, old, r;
+
+	uart = arg;
+
+	ctlr = uart->regs;
+	for(iir = csr8r(ctlr, Iir); !(iir & Ip); iir = csr8r(ctlr, Iir)){
+		switch(iir & IirMASK){
+		case Ims:		/* Ms interrupt */
+			r = csr8r(ctlr, Msr);
+			if(r & Dcts){
+				ilock(&uart->tlock);
+				old = uart->cts;
+				uart->cts = r & Cts;
+				if(old == 0 && uart->cts)
+					uart->ctsbackoff = 2;
+				iunlock(&uart->tlock);
+			}
+		 	if(r & Ddsr){
+				old = r & Dsr;
+				if(uart->hup_dsr && uart->dsr && !old)
+					uart->dohup = 1;
+				uart->dsr = old;
+			}
+		 	if(r & Ddcd){
+				old = r & Dcd;
+				if(uart->hup_dcd && uart->dcd && !old)
+					uart->dohup = 1;
+				uart->dcd = old;
+			}
+			break;
+		case Ithre:		/* Thr Empty */
+			uartkick(uart);
+			break;
+		case Irda:		/* Received Data Available */
+		case Irls:		/* Receiver Line Status */
+		case Ictoi:		/* Character Time-out Indication */
+			/*
+			 * Consume any received data.
+			 * If the received byte came in with a break,
+			 * parity or framing error, throw it away;
+			 * overrun is an indication that something has
+			 * already been tossed.
+			 */
+			while((lsr = csr8r(ctlr, Lsr)) & Dr){
+				if(lsr & (FIFOerr|Oe))
+					uart->oerr++;
+				if(lsr & Pe)
+					uart->perr++;
+				if(lsr & Fe)
+					uart->ferr++;
+				r = csr8r(ctlr, Rbr);
+				if(!(lsr & (Bi|Fe|Pe)))
+					uartrecv(uart, r);
+			}
+			break;
+
+		default:
+			iprint("weird uart interrupt 0x%2.2uX\n", iir);
+			break;
+		}
+	}
+}
+
+static void
+i8250disable(Uart* uart)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * Turn off DTR and RTS, disable interrupts and fifos.
+	 */
+	(*uart->phys->dtr)(uart, 0);
+	(*uart->phys->rts)(uart, 0);
+	(*uart->phys->fifo)(uart, 0);
+
+	ctlr = uart->regs;
+	ctlr->sticky[Ier] = 0;
+	csr8w(ctlr, Ier, ctlr->sticky[Ier]);
+	coherence();
+
+	if(ctlr->iena != 0){
+		if(irqdisable(ctlr->irq, i8250interrupt, uart, uart->name) == 0)
+			ctlr->iena = 0;
+	}
+}
+
+static void
+i8250enable(Uart* uart, int ie)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+
+	/*
+	 * Check if there is a FIFO.
+	 * Changing the FIFOena bit in Fcr flushes data
+	 * from both receive and transmit FIFOs; there's
+	 * no easy way to guarantee not losing data on
+	 * the receive side, but it's possible to wait until
+	 * the transmitter is really empty.
+	 * Also, reading the Iir outwith i8250interrupt()
+	 * can be dangerous, but this should only happen
+	 * once, before interrupts are enabled.
+	 */
+	ilock(ctlr);
+	if(!ctlr->checkfifo){
+		/*
+		 * Wait until the transmitter is really empty.
+		 */
+		while(!(csr8r(ctlr, Lsr) & Temt))
+			;
+		csr8w(ctlr, Fcr, FIFOena);
+		if(csr8r(ctlr, Iir) & Ifena)
+			ctlr->hasfifo = 1;
+		csr8w(ctlr, Fcr, 0);
+		coherence();
+		ctlr->checkfifo = 1;
+	}
+	iunlock(ctlr);
+
+	/*
+	 * Enable interrupts and turn on DTR and RTS.
+	 * Be careful if this is called to set up a polled serial line
+	 * early on not to try to enable interrupts as interrupt-
+	 * -enabling mechanisms might not be set up yet.
+	 */
+	if(ie){
+		if(ctlr->iena == 0 && !ctlr->poll){
+			irqenable(ctlr->irq, i8250interrupt, uart, uart->name);
+			ctlr->iena = 1;
+		}
+		ctlr->sticky[Ier] = Ethre|Erda;
+		ctlr->sticky[Mcr] |= Ie;
+	}
+	else{
+		ctlr->sticky[Ier] = 0;
+		ctlr->sticky[Mcr] = 0;
+	}
+	csr8w(ctlr, Ier, ctlr->sticky[Ier]);
+	csr8w(ctlr, Mcr, ctlr->sticky[Mcr]);
+	coherence();
+
+	(*uart->phys->dtr)(uart, 1);
+	(*uart->phys->rts)(uart, 1);
+
+	/*
+	 * During startup, the i8259 interrupt controller is reset.
+	 * This may result in a lost interrupt from the i8250 uart.
+	 * The i8250 thinks the interrupt is still outstanding and does not
+	 * generate any further interrupts. The workaround is to call the
+	 * interrupt handler to clear any pending interrupt events.
+	 * Note: this must be done after setting Ier.
+	 */
+	if(ie)
+		i8250interrupt(nil, uart);
+}
+
+static Uart*
+i8250pnp(void)
+{
+	return i8250uart;
+}
+
+static int
+i8250getc(Uart* uart)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	while(!(csr8r(ctlr, Lsr) & Dr))
+		delay(1);
+	return csr8r(ctlr, Rbr);
+}
+
+static void
+i8250putc(Uart* uart, int c)
+{
+	int i;
+	Ctlr *ctlr;
+
+	if (!normalprint) {	/* too early; use brute force */
+		int s = splhi();
+
+		while (!(((ulong *)PHYSCONS)[Lsr] & Thre))
+			;
+		((ulong *)PHYSCONS)[Thr] = c;
+		coherence();
+		splx(s);
+		return;
+	}
+
+	ctlr = uart->regs;
+	for(i = 0; !(csr8r(ctlr, Lsr) & Thre) && i < 128; i++)
+		delay(1);
+	csr8o(ctlr, Thr, c);
+	coherence();
+	for(i = 0; !(csr8r(ctlr, Lsr) & Thre) && i < 128; i++)
+		delay(1);
+}
+
+void
+serialputc(int c)
+{
+	i8250putc(&i8250uart[CONSOLE], c);
+}
+
+void
+serialputs(char* s, int n)
+{
+	_uartputs(s, n);
+}
+
+static void
+i8250poll(Uart* uart)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * If PhysUart has a non-nil .poll member, this
+	 * routine will be called from the uartclock timer.
+	 * If the Ctlr .poll member is non-zero, when the
+	 * Uart is enabled interrupts will not be enabled
+	 * and the result is polled input and output.
+	 * Not very useful here, but ports to new hardware
+	 * or simulators can use this to get serial I/O
+	 * without setting up the interrupt mechanism.
+	 */
+	ctlr = uart->regs;
+	if(ctlr->iena || !ctlr->poll)
+		return;
+	i8250interrupt(nil, uart);
+}
+
+PhysUart i8250physuart = {
+	.name		= "i8250",
+	.pnp		= i8250pnp,
+	.enable		= i8250enable,
+	.disable	= i8250disable,
+	.kick		= i8250kick,
+	.dobreak	= i8250break,
+	.baud		= i8250baud,
+	.bits		= i8250bits,
+	.stop		= i8250stop,
+	.parity		= i8250parity,
+	.modemctl	= i8250modemctl,
+	.rts		= i8250rts,
+	.dtr		= i8250dtr,
+	.status		= i8250status,
+	.fifo		= i8250fifo,
+	.getc		= i8250getc,
+	.putc		= i8250putc,
+//	.poll		= i8250poll,
+};
+
+static void
+i8250dumpregs(Ctlr* ctlr)
+{
+	int dlm, dll;
+	int _uartprint(char*, ...);
+
+	csr8w(ctlr, Lcr, Dlab);
+	coherence();
+	dlm = csr8r(ctlr, Dlm);
+	dll = csr8r(ctlr, Dll);
+	csr8w(ctlr, Lcr, 0);
+	coherence();
+
+	_uartprint("dlm %#ux dll %#ux\n", dlm, dll);
+}
+
+void
+i8250console(void)
+{
+	Uart *uart = &i8250uart[CONSOLE];
+
+//	(*uart->phys->enable)(uart, 0);
+//	uartctl(uart, "b115200 l8 pn s1 i1");
+//	uart->console = 1;
+	consuart = uart;
+}
+
+void
+_uartputs(char* s, int n)
+{
+	char *e;
+
+	for(e = s+n; s < e; s++){
+		if(*s == '\n')
+			i8250putc(&i8250uart[CONSOLE], '\r');
+		i8250putc(&i8250uart[CONSOLE], *s);
+	}
+}
+
+int
+_uartprint(char* fmt, ...)
+{
+	int n;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	va_start(arg, fmt);
+	n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	_uartputs(buf, n);
+
+	return n;
+}

+ 152 - 0
sys/src/9/omap/ucallocb.c

@@ -0,0 +1,152 @@
+/*
+ * allocate Blocks from uncached memory
+ */
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Hdrspc		= 64,		/* leave room for high-level headers */
+	Bdead		= 0x51494F42,	/* "QIOB" */
+};
+
+struct
+{
+	Lock;
+	ulong	bytes;
+} ucialloc;
+
+static Block*
+_ucallocb(int size)
+{
+	Block *b;
+	ulong addr;
+
+	if((b = ucalloc(sizeof(Block)+size+Hdrspc)) == nil)
+		return nil;
+
+	b->next = nil;
+	b->list = nil;
+	b->free = 0;
+	b->flag = 0;
+	b->ref = 0;
+	_xinc(&b->ref);
+
+	/* align start of data portion by rounding up */
+	addr = (ulong)b;
+	addr = ROUND(addr + sizeof(Block), BLOCKALIGN);
+	b->base = (uchar*)addr;
+
+	/* align end of data portion by rounding down */
+	b->lim = ((uchar*)b) + msize(b);
+	addr = (ulong)(b->lim);
+	addr = addr & ~(BLOCKALIGN-1);
+	b->lim = (uchar*)addr;
+
+	/* leave sluff at beginning for added headers */
+	b->rp = b->lim - ROUND(size, BLOCKALIGN);
+	if(b->rp < b->base)
+		panic("_ucallocb");
+	b->wp = b->rp;
+
+	return b;
+}
+
+Block*
+ucallocb(int size)
+{
+	Block *b;
+
+	/*
+	 * Check in a process and wait until successful.
+	 * Can still error out of here, though.
+	 */
+	if(up == nil)
+		panic("ucallocb without up: %#p", getcallerpc(&size));
+	if((b = _ucallocb(size)) == nil)
+		panic("ucallocb: no memory for %d bytes", size);
+	setmalloctag(b, getcallerpc(&size));
+
+	return b;
+}
+
+Block*
+uciallocb(int size)
+{
+	Block *b;
+	static int m1, m2, mp;
+
+	if(0 && ucialloc.bytes > conf.ialloc){
+		if((m1++%10000)==0){
+			if(mp++ > 1000){
+				active.exiting = 1;
+				exit(0);
+			}
+			iprint("uciallocb: limited %lud/%lud\n",
+				ucialloc.bytes, conf.ialloc);
+		}
+		return nil;
+	}
+
+	if((b = _ucallocb(size)) == nil){
+		if(0 && (m2++%10000)==0){
+			if(mp++ > 1000){
+				active.exiting = 1;
+				exit(0);
+			}
+			iprint("uciallocb: no memory %lud/%lud\n",
+				ucialloc.bytes, conf.ialloc);
+		}
+		return nil;
+	}
+	setmalloctag(b, getcallerpc(&size));
+	b->flag = BINTR;
+
+	ilock(&ucialloc);
+	ucialloc.bytes += b->lim - b->base;
+	iunlock(&ucialloc);
+
+	return b;
+}
+
+void
+ucfreeb(Block *b)
+{
+	void *dead = (void*)Bdead;
+	long ref;
+
+	if(b == nil || (ref = _xdec(&b->ref)) > 0)
+		return;
+
+	if(ref < 0){
+		dumpstack();
+		panic("ucfreeb: ref %ld; caller pc %#p", ref, getcallerpc(&b));
+	}
+
+	/*
+	 * drivers which perform non cache coherent DMA manage their own buffer
+	 * pool of uncached buffers and provide their own free routine.
+	 */
+	if(b->free) {
+		b->free(b);
+		return;
+	}
+	if(b->flag & BINTR) {
+		ilock(&ucialloc);
+		ucialloc.bytes -= b->lim - b->base;
+		iunlock(&ucialloc);
+	}
+
+	/* poison the block in case someone is still holding onto it */
+	b->next = dead;
+	b->rp = dead;
+	b->wp = dead;
+	b->lim = dead;
+	b->base = dead;
+
+	ucfree(b);
+}

+ 4 - 0
sys/src/9/omap/uncached.h

@@ -12,6 +12,10 @@
 #define xspanalloc		ucallocalign
 #define mallocz(n, clr)		ucallocz(n, clr)
 
+#define allocb			ucallocb
+#define iallocb			uciallocb
+#define freeb			ucfreeb
+
 static void *
 ucallocz(uint n, int)
 {

+ 194 - 0
sys/src/9/omap/usb.h

@@ -0,0 +1,194 @@
+/*
+ * common USB definitions.
+ */
+typedef struct Udev Udev;	/* USB device */
+typedef struct Ep Ep;		/* Endpoint */
+typedef struct Hci Hci;		/* Host Controller Interface */
+typedef struct Hciimpl Hciimpl;	/* Link to the controller impl. */
+
+enum
+{
+	/* fundamental constants */
+	Ndeveps	= 16,		/* max nb. of endpoints per device */
+
+	/* tunable parameters */
+	Nhcis	= 16,		/* max nb. of HCIs */
+	Neps	= 64,		/* max nb. of endpoints */
+	Maxctllen = 16*1024,	/* max allowed sized for ctl. xfers */
+	Xfertmout = 2000,	/* default request time out (ms) */
+
+	/* transfer types. keep this order */
+	Tnone = 0,		/* no tranfer type configured */
+	Tctl,			/* wr req + rd/wr data + wr/rd sts */
+	Tiso,			/* stream rd or wr (real time) */
+	Tbulk,			/* stream rd or wr */
+	Tintr,			/* msg rd or wr */
+	Nttypes,		/* number of transfer types */
+
+	Epmax	= 0xF,		/* max ep. addr */
+	Devmax	= 0x7F,		/* max dev. addr */
+
+	/* Speeds */
+	Fullspeed = 0,
+	Lowspeed,
+	Highspeed,
+	Nospeed,
+
+	/* request type */
+	Rh2d = 0<<7,
+	Rd2h = 1<<7,
+	Rstd = 0<<5,
+	Rclass =  1<<5,
+	Rdev = 0,
+	Rep = 2,
+	Rother = 3,
+
+	/* req offsets */
+	Rtype	= 0,
+	Rreq	= 1,
+	Rvalue	= 2,
+	Rindex	= 4,
+	Rcount	= 6,
+	Rsetuplen = 8,
+
+	/* standard requests */
+	Rgetstatus	= 0,
+	Rclearfeature	= 1,
+	Rsetfeature	= 3,
+	Rsetaddr	= 5,
+	Rgetdesc	= 6,
+
+	/* device states */
+	Dconfig	 = 0,		/* configuration in progress */
+	Denabled,		/* address assigned */
+	Ddetach,		/* device is detached */
+	Dreset,			/* its port is being reset */
+
+	/* (root) Hub reply to port status (reported to usbd) */
+	HPpresent	= 0x1,
+	HPenable	= 0x2,
+	HPsuspend	= 0x4,
+	HPovercurrent	= 0x8,
+	HPreset		= 0x10,
+	HPpower		= 0x100,
+	HPslow		= 0x200,
+	HPhigh		= 0x400,
+	HPstatuschg	= 0x10000,
+	HPchange	= 0x20000,
+};
+
+/*
+ * Services provided by the driver.
+ * epopen allocates hardware structures to prepare the endpoint
+ * for I/O. This happens when the user opens the data file.
+ * epclose releases them. This happens when the data file is closed.
+ * epwrite tries to write the given bytes, waiting until all of them
+ * have been written (or failed) before returning; but not for Iso.
+ * epread does the same for reading.
+ * It can be assumed that endpoints are DMEXCL but concurrent
+ * read/writes may be issued and the controller must take care.
+ * For control endpoints, device-to-host requests must be followed by
+ * a read of the expected length if needed.
+ * The port requests are called when usbd issues commands for root
+ * hubs. Port status must return bits as a hub request would do.
+ * Toggle handling and other details are left for the controller driver
+ * to avoid mixing too much the controller and the comon device.
+ * While an endpoint is closed, its toggles are saved in the Ep struct.
+ */
+struct Hciimpl
+{
+	void	*aux;				/* for controller info */
+	void	(*init)(Hci*);			/* init. controller */
+	void	(*dump)(Hci*);			/* debug */
+	void	(*interrupt)(Ureg*, void*);	/* service interrupt */
+	void	(*epopen)(Ep*);			/* prepare ep. for I/O */
+	void	(*epclose)(Ep*);		/* terminate I/O on ep. */
+	long	(*epread)(Ep*,void*,long);	/* transmit data for ep */
+	long	(*epwrite)(Ep*,void*,long);	/* receive data for ep */
+	char*	(*seprintep)(char*,char*,Ep*);	/* debug */
+	int	(*portenable)(Hci*, int, int);	/* enable/disable port */
+	int	(*portreset)(Hci*, int, int);	/* set/clear port reset */
+	int	(*portstatus)(Hci*, int);	/* get port status */
+	void	(*shutdown)(Hci*);		/* shutdown for reboot */
+	void	(*debug)(Hci*, int);		/* set/clear debug flag */
+};
+
+struct Hci
+{
+	ISAConf;				/* hardware info */
+//	int	tbdf;				/* type+busno+devno+funcno */
+	int	ctlrno;				/* controller number */
+	int	nports;				/* number of ports in hub */
+	int	highspeed;
+	Hciimpl;					/* HCI driver  */
+};
+
+/*
+ * USB endpoint.
+ * All endpoints are kept in a global array. The first
+ * block of fields is constant after endpoint creation.
+ * The rest is configuration information given to all controllers.
+ * The first endpoint for a device (known as ep0) represents the
+ * device and is used to configure it and create other endpoints.
+ * Its QLock also protects per-device data in dev.
+ * See Hciimpl for clues regarding how this is used by controllers.
+ */
+struct Ep
+{
+	Ref;			/* one per fid (and per dev ep for ep0s) */
+
+	/* const once inited. */
+	int	idx;		/* index in global eps array */
+	int	nb;		/* endpoint number in device */
+	Hci*	hp;		/* HCI it belongs to */
+	Udev*	dev;		/* device for the endpoint */
+	Ep*	ep0;		/* control endpoint for its device */
+
+	QLock;			/* protect fields below */
+	char*	name;		/* for ep file names at #u/ */
+	int	inuse;		/* endpoint is open */
+	int	mode;		/* OREAD, OWRITE, or ORDWR */
+	int	clrhalt;	/* true if halt was cleared on ep. */
+	int	debug;		/* per endpoint debug flag */
+	char*	info;		/* for humans to read */
+	long	maxpkt;		/* maximum packet size */
+	int	ttype;		/* tranfer type */
+	ulong	load;		/* in µs, for a fransfer of maxpkt bytes */
+	void*	aux;		/* for controller specific info */
+	int	rhrepl;		/* fake root hub replies */
+	int	toggle[2];	/* saved toggles (while ep is not in use) */
+	long	pollival;		/* poll interval ([µ]frames; intr/iso) */
+	long	hz;		/* poll frequency (iso) */
+	long	samplesz;	/* sample size (iso) */
+	int	ntds;		/* nb. of Tds per µframe */
+	int	tmout;		/* 0 or timeout for transfers (ms) */
+};
+
+/*
+ * Per-device configuration and cached list of endpoints.
+ * eps[0]->QLock protects it.
+ */
+struct Udev
+{
+	int	nb;		/* USB device number */
+	int	state;		/* state for the device */
+	int	ishub;		/* hubs can allocate devices */
+	int	isroot;		/* is a root hub */
+	int	speed;		/* Full/Low/High/No -speed */
+	int	hub;		/* dev number for the parent hub */
+	int	port;		/* port number in the parent hub */
+	Ep*	eps[Ndeveps];	/* end points for this device (cached) */
+};
+
+void	addhcitype(char *type, int (*reset)(Hci*));
+#define dprint		if(debug)print
+#define ddprint		if(debug>1)print
+#define deprint		if(debug || ep->debug)print
+#define ddeprint	if(debug>1 || ep->debug>1)print
+#define	GET2(p)		((((p)[1]&0xFF)<<8)|((p)[0]&0xFF))
+#define	PUT2(p,v)	{((p)[0] = (v)); ((p)[1] = (v)>>8);}
+
+extern char *usbmodename[];
+extern char Estalled[];
+
+extern char *seprintdata(char*,char*,uchar*,int);

+ 3377 - 0
sys/src/9/omap/usbehci.c

@@ -0,0 +1,3377 @@
+/*
+ * USB Enhanced Host Controller Interface (EHCI) driver
+ * High speed USB 2.0.
+ *
+ * BUGS:
+ * - Too many delays and ilocks.
+ * - bandwidth admission control must be done per-frame.
+ * - requires polling (some controllers miss interrupts).
+ * - must warn of power overruns.
+ */
+
+#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"
+#include	"uncached.h"
+
+typedef struct Ctlio Ctlio;
+typedef struct Ctlr Ctlr;
+typedef union Ed Ed;
+typedef struct Edpool Edpool;
+typedef struct Fstn Fstn;
+typedef struct Isoio Isoio;
+typedef struct Itd Itd;
+typedef struct Poll Poll;
+typedef struct Qh Qh;
+typedef struct Qio Qio;
+typedef struct Qtd Qtd;
+typedef struct Qtree Qtree;
+typedef struct Sitd Sitd;
+typedef struct Td Td;
+
+/*
+ * EHCI interface registers and bits
+ */
+enum
+{
+	/* Queue states (software) */
+	Qidle		= 0,
+	Qinstall,
+	Qrun,
+	Qdone,
+	Qclose,
+	Qfree,
+
+	Enabledelay	= 100,		/* waiting for a port to enable */
+	Abortdelay	= 5,		/* delay after cancelling Tds (ms) */
+
+	Incr		= 64,		/* for pools of Tds, Qhs, etc. */
+	Align		= 128,		/* in bytes for all those descriptors */
+
+	/* Keep them as a power of 2, lower than ctlr->nframes */
+	/* Also, keep Nisoframes >= Nintrleafs */
+	Nintrleafs	= 32,		/* nb. of leaf frames in intr. tree */
+	Nisoframes	= 64,		/* nb. of iso frames (in window) */
+
+	/*
+	 * HW constants
+	 */
+
+	/* Itd bits (csw[]) */
+	Itdactive	= 0x80000000,	/* execution enabled */
+	Itddberr	= 0x40000000,	/* data buffer error */
+	Itdbabble	= 0x20000000,	/* babble error */
+	Itdtrerr	= 0x10000000,	/* transaction error */
+	Itdlenshift	= 16,		/* transaction length */
+	Itdlenmask	= 0xFFF,
+	Itdioc		= 0x00008000,	/* interrupt on complete */
+	Itdpgshift	= 12,		/* page select field */
+	Itdoffshift	= 0,		/* transaction offset */
+	/* Itd bits, buffer[] */
+	Itdepshift	= 8,		/* endpoint address (buffer[0]) */
+	Itddevshift	= 0,		/* device address (buffer[0]) */
+	Itdin		= 0x800,	/* is input (buffer[1]) */
+	Itdout		= 0,
+	Itdmaxpktshift	= 0,		/* max packet (buffer[1]) */
+	Itdntdsshift	= 0,		/* nb. of tds per µframe (buffer[2]) */
+
+	Itderrors	= Itddberr|Itdbabble|Itdtrerr,
+
+	/* Sitd bits (epc) */
+	Stdin		= 0x80000000,	/* input direction */
+	Stdportshift	= 24,		/* hub port number */
+	Stdhubshift	= 16,		/* hub address */
+	Stdepshift	= 8,		/* endpoint address */
+	Stddevshift	= 0,		/* device address */
+	/* Sitd bits (mfs) */
+	Stdssmshift	= 0,		/* split start mask */
+	Stdscmshift	= 8,		/* split complete mask */
+	/* Sitd bits (csw) */
+	Stdioc		= 0x80000000,	/* interrupt on complete */
+	Stdpg		= 0x40000000,	/* page select */
+	Stdlenshift	= 16,		/* total bytes to transfer */
+	Stdlenmask	= 0x3FF,
+	Stdactive	= 0x00000080,	/* active */
+	Stderr		= 0x00000040,	/* tr. translator error */
+	Stddberr	= 0x00000020,	/* data buffer error */
+	Stdbabble	= 0x00000010,	/* babble error */
+	Stdtrerr	= 0x00000008,	/* transanction error */
+	Stdmmf		= 0x00000004,	/* missed µframe */
+	Stddcs		= 0x00000002,	/* do complete split */
+
+	Stderrors	= Stderr|Stddberr|Stdbabble|Stdtrerr|Stdmmf,
+
+	/* Sitd bits buffer[1] */
+	Stdtpall	= 0x00000000,	/* all payload here (188 bytes) */
+	Stdtpbegin	= 0x00000008,	/* first payload for fs trans. */
+	Stdtcntmask	= 0x00000007,	/* T-count */
+
+	/* Td bits (csw) */
+	Tddata1		= 0x80000000,	/* data toggle 1 */
+	Tddata0		= 0x00000000,	/* data toggle 0 */
+	Tdlenshift	= 16,		/* total bytes to transfer */
+	Tdlenmask	= 0x7FFF,
+	Tdmaxpkt	= 0x5000,	/* max buffer for a Td */
+	Tdioc		= 0x00008000,	/* interrupt on complete */
+	Tdpgshift	= 12,		/* current page */
+	Tdpgmask	= 7,
+	Tderr1		= 0x00000400,	/* bit 0 of error counter */
+	Tderr2		= 0x00000800,	/* bit 1 of error counter */
+	Tdtokout	= 0x00000000,	/* direction out */
+	Tdtokin		= 0x00000100,	/* direction in */
+	Tdtoksetup	= 0x00000200,	/* setup packet */
+	Tdtok		= 0x00000300,	/* token bits */
+	Tdactive		= 0x00000080,	/* active */
+	Tdhalt		= 0x00000040,	/* halted */
+	Tddberr		= 0x00000020,	/* data buffer error */
+	Tdbabble	= 0x00000010,	/* babble error */
+	Tdtrerr		= 0x00000008,	/* transanction error */
+	Tdmmf		= 0x00000004,	/* missed µframe */
+	Tddcs		= 0x00000002,	/* do complete split */
+	Tdping		= 0x00000001,	/* do ping */
+
+	Tderrors	= Tdhalt|Tddberr|Tdbabble|Tdtrerr|Tdmmf,
+
+	/* Qh bits (eps0) */
+	Qhrlcmask	= 0xF,		/* nak reload count */
+	Qhrlcshift	= 28,		/* nak reload count */
+	Qhnhctl		= 0x08000000,	/* not-high speed ctl */
+	Qhmplmask	= 0x7FF,	/* max packet */
+	Qhmplshift	= 16,
+	Qhhrl		= 0x00008000,	/* head of reclamation list */
+	Qhdtc		= 0x00004000,	/* data toggle ctl. */
+	Qhint		= 0x00000080,	/* inactivate on next transition */
+	Qhspeedmask	= 0x00003000,	/* speed bits */
+	Qhfull		= 0x00000000,	/* full speed */
+	Qhlow		= 0x00001000,	/* low speed */
+	Qhhigh		= 0x00002000,	/* high speed */
+
+	/* Qh bits (eps1) */
+	Qhmultshift	= 30,		/* multiple tds per µframe */
+	Qhmultmask	= 3,
+	Qhportshift	= 23,		/* hub port number */
+	Qhhubshift	= 16,		/* hub address */
+	Qhscmshift	= 8,		/* split completion mask bits */
+	Qhismshift	= 0,		/* interrupt sched. mask bits */
+};
+
+/*
+ * Endpoint tree (software)
+ */
+struct Qtree
+{
+	int	nel;
+	int	depth;
+	ulong*	bw;
+	Qh**	root;
+};
+
+/*
+ * One per endpoint per direction, to control I/O.
+ */
+struct Qio
+{
+	QLock;			/* for the entire I/O process */
+	Rendez;			/* wait for completion */
+	Qh*	qh;		/* Td list (field const after init) */
+	int	usbid;		/* usb address for endpoint/device */
+	int	toggle;		/* Tddata0/Tddata1 */
+	int	tok;		/* Tdtoksetup, Tdtokin, Tdtokout */
+	ulong	iotime;		/* last I/O time; to hold interrupt polls */
+	int	debug;		/* debug flag from the endpoint */
+	char*	err;		/* error string */
+	char*	tag;		/* debug (no room in Qh for this) */
+	ulong	bw;
+};
+
+struct Ctlio
+{
+	Qio;			/* a single Qio for each RPC */
+	uchar*	data;		/* read from last ctl req. */
+	int	ndata;		/* number of bytes read */
+};
+
+struct Isoio
+{
+	QLock;
+	Rendez;			/* wait for space/completion/errors */
+	int	usbid;		/* address used for device/endpoint */
+	int	tok;		/* Tdtokin or Tdtokout */
+	int	state;		/* Qrun -> Qdone -> Qrun... -> Qclose */
+	int	nframes;	/* number of frames ([S]Itds) used */
+	uchar*	data;		/* iso data buffers if not embedded */
+	char*	err;		/* error string */
+	int	nerrs;		/* nb of consecutive I/O errors */
+	ulong	maxsize;		/* ntds * ep->maxpkt */
+	long	nleft;		/* number of bytes left from last write */
+	int	debug;		/* debug flag from the endpoint */
+	int	hs;		/* is high speed? */
+	Isoio*	next;		/* in list of active Isoios */
+	ulong	td0frno;	/* first frame used in ctlr */
+	union{
+		Itd*	tdi;	/* next td processed by interrupt */
+		Sitd*	stdi;
+	};
+	union{
+		Itd*	tdu;	/* next td for user I/O in tdps */
+		Sitd*	stdu;
+	};
+	union{
+		Itd**	itdps;	/* itdps[i]: ptr to Itd for i-th frame or nil */
+		Sitd**	sitdps;	/* sitdps[i]: ptr to Sitd for i-th frame or nil */
+		ulong**	tdps;	/* same thing, as seen by hw */
+	};
+};
+
+struct Poll
+{
+	Lock;
+	Rendez;
+	int must;
+	int does;
+};
+
+struct Ctlr
+{
+	Rendez;			/* for waiting to async advance doorbell */
+	Lock;			/* for ilock. qh lists and basic ctlr I/O */
+	QLock	portlck;	/* for port resets/enable... (and doorbell) */
+	int	active;		/* in use or not */
+//	Pcidev*	pcidev;
+	Ecapio*	capio;		/* Capability i/o regs */
+	Eopio*	opio;		/* Operational i/o regs */
+
+	int	nframes;	/* 1024, 512, or 256 frames in the list */
+	ulong*	frames;		/* periodic frame list (hw) */
+	Qh*	qhs;		/* async Qh circular list for bulk/ctl */
+	Qtree*	tree;		/* tree of Qhs for the periodic list */
+	int	ntree;		/* number of dummy qhs in tree */
+	Qh*	intrqhs;		/* list of (not dummy) qhs in tree  */
+	Isoio*	iso;		/* list of active Iso I/O */
+	ulong	load;
+	ulong	isoload;
+	int	nintr;		/* number of interrupts attended */
+	int	ntdintr;		/* number of intrs. with something to do */
+	int	nqhintr;		/* number of async td intrs. */
+	int	nisointr;	/* number of periodic td intrs. */
+	int	nreqs;
+	Poll	poll;
+};
+
+struct Edpool
+{
+	Lock;
+	Ed*	free;
+	int	nalloc;
+	int	ninuse;
+	int	nfree;
+};
+
+/*
+ * We use the 64-bit version for Itd, Sitd, Td, and Qh.
+ * If the ehci is 64-bit capable it assumes we are using those
+ * structures even when the system is 32 bits.
+ */
+
+/*
+ * Iso transfer descriptor. hw. 92 bytes, 104 bytes total
+ * aligned to 32.
+ */
+struct Itd
+{
+	ulong	link;		/* to next hw struct */
+	ulong	csw[8];		/* sts/length/pg/off. updated by hw */
+	ulong	buffer[7];	/* buffer pointers, addrs, maxsz */
+	ulong	xbuffer[7];	/* high 32 bits of buffer for 64-bits */
+
+	/* software */
+	Itd*	next;
+	ulong	ndata;		/* number of bytes in data */
+	ulong	mdata;		/* max number of bytes in data */
+	uchar*	data;
+};
+
+/*
+ * Split transaction iso transfer descriptor.
+ * hw: 36 bytes, 52 bytes total. aligned to 32.
+ */
+struct Sitd
+{
+	ulong	link;		/* to next hw struct */
+	ulong	epc;		/* static endpoint state. addrs */
+	ulong	mfs;		/* static endpoint state. µ-frame sched. */
+	ulong	csw;		/* transfer state. updated by hw */
+	ulong	buffer[2];	/* buf. ptr/offset. offset updated by hw */
+				/* buf ptr/TP/Tcnt. TP/Tcnt updated by hw */
+	ulong	blink;		/* back pointer */
+	ulong	xbuffer[2];	/* high 32 bits of buffer for 64-bits */
+
+	/* software */
+	Sitd*	next;
+	ulong	ndata;		/* number of bytes in data */
+	ulong	mdata;		/* max number of bytes in data */
+	uchar*	data;
+};
+
+/*
+ * Queue element transfer descriptor.
+ * hw: first 52 bytes; total 68+sbuff bytes aligned to 32 bytes.
+ */
+struct Td
+{
+	ulong	nlink;		/* to next Td */
+	ulong	alink;		/* alternate link to next Td */
+	ulong	csw;		/* cmd/sts. updated by hw */
+	ulong	buffer[5];	/* buf ptrs. offset updated by hw */
+	ulong	xbuffer[5];	/* high 32 bits of buffer for 64-bits */
+
+	Td*	next;		/* in qh or Isoio or free list */
+	ulong	ndata;		/* bytes available/used at data */
+	uchar*	data;		/* pointer to actual data */
+	uchar*	buff;		/* allocated data buffer or nil */
+	uchar	sbuff[1];	/* first byte of embedded buffer */
+};
+
+/*
+ * Queue head. Aligned to 32 bytes.
+ * hw uses the first 68 bytes, 92 total.
+ */
+struct Qh
+{
+	ulong	link;		/* to next Qh in round robin */
+	ulong	eps0;		/* static endpoint state. addrs */
+	ulong	eps1;		/* static endpoint state. µ-frame sched. */
+
+	/* updated by hw */
+	ulong	tclink;		/* current Td (No Term bit here!) */
+	ulong	nlink;		/* to next Td */
+	ulong	alink;		/* alternate link to next Td */
+	ulong	csw;		/* cmd/sts. updated by hw */
+	ulong	buffer[5];	/* buf ptrs. offset updated by hw */
+	ulong	xbuffer[5];	/* high 32 bits of buffer for 64-bits */
+
+	/* software */
+	Qh*	next;		/* in controller list/tree of Qhs */
+	int	state;		/* Qidle -> Qinstall -> Qrun -> Qdone | Qclose */
+	Qio*	io;		/* for this queue */
+	Td*	tds;		/* for this queue */
+	int	sched;		/* slot for for intr. Qhs */
+	Qh*	inext;		/* next in list of intr. qhs */
+};
+
+/*
+ * We can avoid frame span traversal nodes if we don't span frames.
+ * Just schedule transfer that can fit on the current frame and
+ * wait a little bit otherwise.
+ */
+
+/*
+ * Software. Ehci descriptors provided by pool.
+ * There are soo few because we avoid using Fstn.
+ */
+union Ed
+{
+	Ed*	next;		/* in free list */
+	Qh	qh;
+	Td	td;
+	Itd	itd;
+	Sitd	sitd;
+	uchar	align[Align];
+};
+
+#define diprint		if(debug || iso->debug)print
+#define ddiprint		if(debug>1 || iso->debug>1)print
+#define dqprint		if(debug || (qh->io && qh->io->debug))print
+#define ddqprint		if(debug>1 || (qh->io && qh->io->debug>1))print
+#define TRUNC(x, sz)	((x) & ((sz)-1))
+#define LPTR(q)		((ulong*)KADDR((q) & ~0x1F))
+
+static int debug = 0;
+static Edpool edpool;
+static Ctlr* ctlrs[Nhcis];
+static char Ebug[] = "not yet implemented";
+static char* qhsname[] = { "idle", "install", "run", "done", "close", "FREE" };
+
+Ecapio* ehcidebugcapio;
+int ehcidebugport;
+
+static void
+ehcirun(Ctlr *ctlr, int on)
+{
+	int i;
+	Eopio *opio;
+
+	ddprint("ehcirun: %#p %s\n", ctlr->capio, on ? "starting" : "halting");
+	opio = ctlr->opio;
+	if(on)
+		opio->cmd |= Crun;
+	else
+		opio->cmd = Cstop;
+	coherence();
+	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)
+		print("ehcirun: %#p %s cmd timed out\n",
+			ctlr->capio, on ? "run" : "halt");
+	ddprint("ehcirun: %#p cmd %#ulx sts %#ulx\n",
+		ctlr->capio, opio->cmd, opio->sts);
+}
+
+static void*
+edalloc(void)
+{
+	Ed *ed, *pool;
+	int i;
+
+	lock(&edpool);
+	if(edpool.free == nil){
+		pool = xspanalloc(Incr*sizeof(Ed), Align, 0);
+		if(pool == nil)
+			panic("edalloc");
+		for(i=Incr; --i>=0;){
+			pool[i].next = edpool.free;
+			edpool.free = &pool[i];
+		}
+		edpool.nalloc += Incr;
+		edpool.nfree += Incr;
+		dprint("ehci: edalloc: %d eds\n", edpool.nalloc);
+	}
+	ed = edpool.free;
+	edpool.free = ed->next;
+	edpool.ninuse++;
+	edpool.nfree--;
+	unlock(&edpool);
+
+	memset(ed, 0, sizeof(Ed));	/* safety */
+	assert(((ulong)ed & 0xF) == 0);
+	return ed;
+}
+
+static void
+edfree(void *a)
+{
+	Ed *ed;
+
+	ed = a;
+	lock(&edpool);
+	ed->next = edpool.free;
+	edpool.free = ed;
+	edpool.ninuse--;
+	edpool.nfree++;
+	unlock(&edpool);
+}
+
+/*
+ * Allocate and so same initialization.
+ * Free after releasing buffers used.
+ */
+
+static Itd*
+itdalloc(void)
+{
+	Itd *td;
+
+	td = edalloc();
+	td->link = Lterm;
+	return td;
+}
+
+static void
+itdfree(Itd *td)
+{
+	edfree(td);
+}
+
+static Sitd*
+sitdalloc(void)
+{
+	Sitd *td;
+
+	td = edalloc();
+	td->link = td->blink = Lterm;
+	return td;
+}
+
+static void
+sitdfree(Sitd *td)
+{
+	edfree(td);
+}
+
+static Td*
+tdalloc(void)
+{
+	Td *td;
+
+	td = edalloc();
+	td->nlink = td->alink = Lterm;
+	return td;
+}
+
+static void
+tdfree(Td *td)
+{
+	if(td == nil)
+		return;
+	free(td->buff);
+	edfree(td);
+}
+
+static void
+tdlinktd(Td *td, Td *next)
+{
+	td->next = next;
+	td->alink = Lterm;
+	if(next == nil)
+		td->nlink = Lterm;
+	else
+		td->nlink = PADDR(next);
+}
+
+static Qh*
+qhlinkqh(Qh *qh, Qh *next)
+{
+	qh->next = next;
+	qh->link = PADDR(next)|Lqh;
+	return qh;
+}
+
+static void
+qhsetaddr(Qh *qh, ulong addr)
+{
+	ulong eps0;
+	ulong ep;
+	ulong dev;
+
+	eps0 = qh->eps0 & ~((Epmax<<8)|Devmax);
+	ep = (addr >> 7) & Epmax;
+	dev = addr & Devmax;
+	eps0 |= ep << 8;
+	eps0 |= dev;
+	qh->eps0 = eps0;
+}
+
+/*
+ * return smallest power of 2 <= n
+ */
+static int
+flog2lower(int n)
+{
+	int i;
+
+	for(i = 0; (1 << (i + 1)) <= n; i++)
+		;
+	return i;
+}
+
+static int
+pickschedq(Qtree *qt, int pollival, ulong bw, ulong limit)
+{
+	int i, j, d, upperb, q;
+	ulong best, worst, total;
+
+	d = flog2lower(pollival);
+	if(d > qt->depth)
+		d = qt->depth;
+	q = -1;
+	worst = 0;
+	best = ~0;
+	upperb = (1 << (d+1)) - 1;
+	for(i = (1 << d) - 1; i < upperb; i++){
+		total = qt->bw[0];
+		for(j = i; j > 0; j = (j - 1) / 2)
+			total += qt->bw[j];
+		if(total < best){
+			best = total;
+			q = i;
+		}
+		if(total > worst)
+			worst = total;
+	}
+	if(worst + bw >= limit)
+		return -1;
+	return q;
+}
+
+static int
+schedq(Ctlr *ctlr, Qh *qh, int pollival)
+{
+	int q;
+	Qh *tqh;
+	ulong bw;
+
+	bw = qh->io->bw;
+	q = pickschedq(ctlr->tree, pollival, 0, ~0);
+	ddqprint("ehci: sched %#p q %d, ival %d, bw %uld\n",
+		qh->io, q, pollival, bw);
+	if(q < 0){
+		print("ehci: no room for ed\n");
+		return -1;
+	}
+	ctlr->tree->bw[q] += bw;
+	tqh = ctlr->tree->root[q];
+	qh->sched = q;
+	qhlinkqh(qh, tqh->next);
+	qhlinkqh(tqh, qh);
+	qh->inext = ctlr->intrqhs;
+	ctlr->intrqhs = qh;
+	return 0;
+}
+
+static void
+unschedq(Ctlr *ctlr, Qh *qh)
+{
+	int q;
+	Qh *prev, *this, *next;
+	Qh **l;
+	ulong bw;
+
+	bw = qh->io->bw;
+	q = qh->sched;
+	if(q < 0)
+		return;
+	ctlr->tree->bw[q] -= bw;
+
+	prev = ctlr->tree->root[q];
+	this = prev->next;
+	while(this != nil && this != qh){
+		prev = this;
+		this = this->next;
+	}
+	if(this == nil)
+		print("ehci: unschedq %d: not found\n", q);
+	else{
+		next = this->next;
+		qhlinkqh(prev, next);
+	}
+	for(l = &ctlr->intrqhs; *l != nil; l = &(*l)->inext)
+		if(*l == qh){
+			*l = (*l)->inext;
+			return;
+		}
+	print("ehci: unschedq: qh %#p not found\n", qh);
+}
+
+static ulong
+qhmaxpkt(Qh *qh)
+{
+	return (qh->eps0 >> Qhmplshift) & Qhmplmask;
+}
+
+static void
+qhsetmaxpkt(Qh *qh, int maxpkt)
+{
+	ulong eps0;
+
+	eps0 = qh->eps0 & ~(Qhmplmask << Qhmplshift);
+	eps0 |= (maxpkt & Qhmplmask) << Qhmplshift;
+	qh->eps0 = eps0;
+	coherence();
+}
+
+/*
+ * Initialize the round-robin circular list of ctl/bulk Qhs
+ * if ep is nil. Otherwise, allocate and link a new Qh in the ctlr.
+ */
+static Qh*
+qhalloc(Ctlr *ctlr, Ep *ep, Qio *io, char* tag)
+{
+	Qh *qh;
+	int ttype;
+
+	qh = edalloc();
+	qh->nlink = Lterm;
+	qh->alink = Lterm;
+	qh->csw = Tdhalt;
+	qh->state = Qidle;
+	qh->sched = -1;
+	qh->io = io;
+	if(ep != nil){
+		qh->eps0 = 0;
+		qhsetmaxpkt(qh, ep->maxpkt);
+		if(ep->dev->speed == Lowspeed)
+			qh->eps0 |= Qhlow;
+		if(ep->dev->speed == Highspeed)
+			qh->eps0 |= Qhhigh;
+		else if(ep->ttype == Tctl)
+			qh->eps0 |= Qhnhctl;
+		qh->eps0 |= Qhdtc;
+		qh->eps0 |= (8 << Qhrlcshift);	/* 8 naks max */
+		qhsetaddr(qh, io->usbid);
+		qh->eps1 = (ep->ntds & Qhmultmask) << Qhmultshift;
+		qh->eps1 |= ep->dev->port << Qhportshift;
+		qh->eps1 |= ep->dev->hub << Qhhubshift;
+		qh->eps1 |= 034 << Qhscmshift;
+		if(ep->ttype == Tintr)
+			qh->eps1 |= (1 << Qhismshift); /* intr. start µf. */
+		if(io != nil)
+			io->tag = tag;
+	}
+	ilock(ctlr);
+	ttype = Tctl;
+	if(ep != nil)
+		ttype = ep->ttype;
+	switch(ttype){
+	case Tctl:
+	case Tbulk:
+		if(ctlr->qhs == nil){
+			ctlr->qhs = qhlinkqh(qh, qh);
+			qh->eps0 |= Qhhigh | Qhhrl;
+			coherence();
+			ctlr->opio->link = PADDR(qh)|Lqh;
+			coherence();
+		}else{
+			qhlinkqh(qh, ctlr->qhs->next);
+			qhlinkqh(ctlr->qhs, qh);
+		}
+		break;
+	case Tintr:
+		schedq(ctlr, qh, ep->pollival);
+		break;
+	default:
+		print("ehci: qhalloc called for ttype != ctl/bulk\n");
+	}
+	iunlock(ctlr);
+	return qh;
+}
+
+static int
+qhadvanced(void *a)
+{
+	Ctlr *ctlr;
+
+	ctlr = a;
+	return (ctlr->opio->cmd & Ciasync) == 0;
+}
+
+/*
+ * called when a qh is removed, to be sure the hw is not
+ * keeping pointers into it.
+ */
+static void
+qhcoherency(Ctlr *ctlr)
+{
+	int i;
+
+	qlock(&ctlr->portlck);
+	ctlr->opio->cmd |= Ciasync;	/* ask for intr. on async advance */
+	coherence();
+	for(i = 0; i < 3 && qhadvanced(ctlr) == 0; i++)
+		if(!waserror()){
+			tsleep(ctlr, qhadvanced, ctlr, Abortdelay);
+			poperror();
+		}
+	dprint("ehci: qhcoherency: doorbell %d\n", qhadvanced(ctlr));
+	if(i == 3)
+		print("ehci: async advance doorbell did not ring\n");
+	ctlr->opio->cmd &= ~Ciasync;	/* try to clean */
+	coherence();
+	qunlock(&ctlr->portlck);
+}
+
+static void
+qhfree(Ctlr *ctlr, Qh *qh)
+{
+	Td *td, *ltd;
+	Qh *q;
+
+	if(qh == nil)
+		return;
+	ilock(ctlr);
+	if(qh->sched < 0){
+		for(q = ctlr->qhs; q != nil; q = q->next)
+			if(q->next == qh)
+				break;
+		if(q == nil)
+			panic("qhfree: nil q");
+		q->next = qh->next;
+		q->link = qh->link;
+	}else
+		unschedq(ctlr, qh);
+	iunlock(ctlr);
+
+	qhcoherency(ctlr);
+
+	for(td = qh->tds; td != nil; td = ltd){
+		ltd = td->next;
+		tdfree(td);
+	}
+
+	edfree(qh);
+}
+
+static void
+qhlinktd(Qh *qh, Td *td)
+{
+	ulong csw;
+	int i;
+
+	if(td == nil){
+		qh->tds = nil;
+		qh->csw |= Tdhalt;
+		qh->csw &= ~Tdactive;
+	}else{
+		qh->tds = td;
+		csw = qh->csw & (Tddata1|Tdping);	/* save */
+		qh->csw = Tdhalt;
+		qh->tclink = 0;
+		qh->alink = Lterm;
+		qh->nlink = PADDR(td);
+		for(i = 0; i < nelem(qh->buffer); i++)
+			qh->buffer[i] = 0;
+		qh->csw = csw & ~(Tdhalt|Tdactive);	/* activate next */
+	}
+}
+
+static char*
+seprintlink(char *s, char *se, char *name, ulong l, int typed)
+{
+	s = seprint(s, se, "%s %ulx", name, l);
+	if((l & Lterm) != 0)
+		return seprint(s, se, "T");
+	if(typed == 0)
+		return s;
+	switch(l & (3<<1)){
+	case Litd:
+		return seprint(s, se, "I");
+	case Lqh:
+		return seprint(s, se, "Q");
+	case Lsitd:
+		return seprint(s, se, "S");
+	default:
+		return seprint(s, se, "F");
+	}
+}
+
+static char*
+seprintitd(char *s, char *se, Itd *td)
+{
+	int i;
+	char flags[6];
+	ulong b0;
+	ulong b1;
+	char *rw;
+
+	if(td == nil)
+		return seprint(s, se, "<nil itd>\n");
+	b0 = td->buffer[0];
+	b1 = td->buffer[1];
+
+	s = seprint(s, se, "itd %#p", td);
+	rw = (b1 & Itdin) ? "in" : "out";
+	s = seprint(s, se, " %s ep %uld dev %uld max %uld mult %uld",
+		rw, (b0>>8)&Epmax, (b0&Devmax),
+		td->buffer[1] & 0x7ff, b1 & 3);
+	s = seprintlink(s, se, " link", td->link, 1);
+	s = seprint(s, se, "\n");
+	for(i = 0; i < nelem(td->csw); i++){
+		memset(flags, '-', 5);
+		if((td->csw[i] & Itdactive) != 0)
+			flags[0] = 'a';
+		if((td->csw[i] & Itdioc) != 0)
+			flags[1] = 'i';
+		if((td->csw[i] & Itddberr) != 0)
+			flags[2] = 'd';
+		if((td->csw[i] & Itdbabble) != 0)
+			flags[3] = 'b';
+		if((td->csw[i] & Itdtrerr) != 0)
+			flags[4] = 't';
+		flags[5] = 0;
+		s = seprint(s, se, "\ttd%d %s", i, flags);
+		s = seprint(s, se, " len %uld", (td->csw[i] >> 16) & 0x7ff);
+		s = seprint(s, se, " pg %uld", (td->csw[i] >> 12) & 0x7);
+		s = seprint(s, se, " off %uld\n", td->csw[i] & 0xfff);
+	}
+	s = seprint(s, se, "\tbuffs:");
+	for(i = 0; i < nelem(td->buffer); i++)
+		s = seprint(s, se, " %#ulx", td->buffer[i] >> 12);
+	return seprint(s, se, "\n");
+}
+
+static char*
+seprintsitd(char *s, char *se, Sitd *td)
+{
+	static char pc[4] = { 'a', 'b', 'm', 'e' };
+	char rw;
+	char pg;
+	char ss;
+	char flags[8];
+
+	if(td == nil)
+		return seprint(s, se, "<nil sitd>\n");
+	s = seprint(s, se, "sitd %#p", td);
+	rw = (td->epc & Stdin) ? 'r' : 'w';
+	s = seprint(s, se, " %c ep %uld dev %uld",
+		rw, (td->epc>>8)&0xf, td->epc&0x7f);
+	s = seprint(s, se, " max %uld", (td->csw >> 16) & 0x3ff);
+	s = seprint(s, se, " hub %uld", (td->epc >> 16) & 0x7f);
+	s = seprint(s, se, " port %uld\n", (td->epc >> 24) & 0x7f);
+	memset(flags, '-', 7);
+	if((td->csw & Stdactive) != 0)
+		flags[0] = 'a';
+	if((td->csw & Stdioc) != 0)
+		flags[1] = 'i';
+	if((td->csw & Stderr) != 0)
+		flags[2] = 'e';
+	if((td->csw & Stddberr) != 0)
+		flags[3] = 'd';
+	if((td->csw & Stdbabble) != 0)
+		flags[4] = 'b';
+	if((td->csw & Stdtrerr) != 0)
+		flags[5] = 't';
+	if((td->csw & Stdmmf) != 0)
+		flags[6] = 'n';
+	flags[7] = 0;
+	ss = (td->csw & Stddcs) ? 'c' : 's';
+	pg = (td->csw & Stdpg) ? '1' : '0';
+	s = seprint(s, se, "\t%s %cs pg%c", flags, ss, pg);
+	s = seprint(s, se, " b0 %#ulx b1 %#ulx off %uld\n",
+		td->buffer[0] >> 12, td->buffer[1] >> 12, td->buffer[0] & 0xfff);
+	s = seprint(s, se, "\ttpos %c tcnt %uld",
+		pc[(td->buffer[0]>>3)&3], td->buffer[1] & 7);
+	s = seprint(s, se, " ssm %#ulx csm %#ulx cspm %#ulx",
+		td->mfs & 0xff, (td->mfs>>8) & 0xff, (td->csw>>8) & 0xff);
+	s = seprintlink(s, se, " link", td->link, 1);
+	s = seprintlink(s, se, " blink", td->blink, 0);
+	return seprint(s, se, "\n");
+}
+
+static long
+maxtdlen(Td *td)
+{
+	return (td->csw >> Tdlenshift) & Tdlenmask;
+}
+
+static long
+tdlen(Td *td)
+{
+	if(td->data == nil)
+		return 0;
+	return td->ndata - maxtdlen(td);
+}
+
+static char*
+seprinttd(char *s, char *se, Td *td, char *tag)
+{
+	static char *tok[4] = { "out", "in", "setup", "BUG" };
+	char flags[9];
+	char t;
+	char ss;
+	int i;
+
+	s = seprint(s, se, "%s %#p", tag, td);
+	s = seprintlink(s, se, " nlink", td->nlink, 0);
+	s = seprintlink(s, se, " alink", td->alink, 0);
+	s = seprint(s, se, " %s", tok[(td->csw & Tdtok) >> 8]);
+	if((td->csw & Tdping) != 0)
+		s = seprint(s, se, " png");
+	memset(flags, '-', 8);
+	if((td->csw & Tdactive) != 0)
+		flags[0] = 'a';
+	if((td->csw & Tdioc) != 0)
+		flags[1] = 'i';
+	if((td->csw & Tdhalt) != 0)
+		flags[2] = 'h';
+	if((td->csw & Tddberr) != 0)
+		flags[3] = 'd';
+	if((td->csw & Tdbabble) != 0)
+		flags[4] = 'b';
+	if((td->csw & Tdtrerr) != 0)
+		flags[5] = 't';
+	if((td->csw & Tdmmf) != 0)
+		flags[6] = 'n';
+	if((td->csw & (Tderr2|Tderr1)) == 0)
+		flags[7] = 'z';
+	flags[8] = 0;
+	t = (td->csw & Tddata1) ? '1' : '0';
+	ss = (td->csw & Tddcs) ? 'c' : 's';
+	s = seprint(s, se, "\n\td%c %s %cs", t, flags, ss);
+	s = seprint(s, se, " max %uld", maxtdlen(td));
+	s = seprint(s, se, " pg %uld off %#ulx\n",
+		(td->csw >> Tdpgshift) & Tdpgmask, td->buffer[0] & 0xFFF);
+	s = seprint(s, se, "\tbuffs:");
+	for(i = 0; i < nelem(td->buffer); i++)
+		s = seprint(s, se, " %#ulx", td->buffer[i]>>12);
+	if(td->data != nil)
+		s = seprintdata(s, se, td->data, td->ndata);
+	return seprint(s, se, "\n");
+}
+
+static void
+dumptd(Td *td, char *pref)
+{
+	char buf[256];
+	char *se;
+	int i;
+
+	i = 0;
+	se = buf+sizeof(buf);
+	for(; td != nil; td = td->next){
+		seprinttd(buf, se, td, pref);
+		print("%s", buf);
+		if(i++ > 20){
+			print("...more tds...\n");
+			break;
+		}
+	}
+}
+
+static void
+qhdump(Qh *qh)
+{
+	static char *speed[] = {"full", "low", "high", "BUG"};
+	char buf[256];
+	char *s;
+	char *se;
+	char *tag;
+	Td td;
+
+	if(qh == nil){
+		print("<nil qh>\n");
+		return;
+	}
+	if(qh->io == nil)
+		tag = "qh";
+	else
+		tag = qh->io->tag;
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "%s %#p", tag, qh);
+	s = seprint(s, se, " ep %uld dev %uld",
+		(qh->eps0>>8)&0xf, qh->eps0&0x7f);
+	s = seprint(s, se, " hub %uld", (qh->eps1 >> 16) & 0x7f);
+	s = seprint(s, se, " port %uld", (qh->eps1 >> 23) & 0x7f);
+	s = seprintlink(s, se, " link", qh->link, 1);
+	seprint(s, se, "  clink %#ulx", qh->tclink);
+	print("%s\n", buf);
+	s = seprint(buf, se, "\tnrld %uld", (qh->eps0 >> Qhrlcshift) & Qhrlcmask);
+	s = seprint(s, se, " nak %uld", (qh->alink >> 1) & 0xf);
+	s = seprint(s, se, " max %uld ", qhmaxpkt(qh));
+	if((qh->eps0 & Qhnhctl) != 0)
+		s = seprint(s, se, "c");
+	if((qh->eps0 & Qhhrl) != 0)
+		s = seprint(s, se, "h");
+	if((qh->eps0 & Qhdtc) != 0)
+		s = seprint(s, se, "d");
+	if((qh->eps0 & Qhint) != 0)
+		s = seprint(s, se, "i");
+	s = seprint(s, se, " %s", speed[(qh->eps0 >> 12) & 3]);
+	s = seprint(s, se, " mult %uld", (qh->eps1 >> Qhmultshift) & Qhmultmask);
+	seprint(s, se, " scm %#ulx ism %#ulx\n",
+		(qh->eps1 >> 8 & 0xff), qh->eps1 & 0xff);
+	print("%s\n", buf);
+	memset(&td, 0, sizeof(td));
+	memmove(&td, &qh->nlink, 32);	/* overlay area */
+	seprinttd(buf, se, &td, "\tovl");
+	print("%s", buf);
+}
+
+static void
+isodump(Isoio* iso, int all)
+{
+	Itd *td, *tdi, *tdu;
+	Sitd *std, *stdi, *stdu;
+	char buf[256];
+	int i;
+
+	if(iso == nil){
+		print("<nil iso>\n");
+		return;
+	}
+	print("iso %#p %s %s speed state %d nframes %d maxsz %uld",
+		iso, iso->tok == Tdtokin ? "in" : "out",
+		iso->hs ? "high" : "full",
+		iso->state, iso->nframes, iso->maxsize);
+	print(" td0 %uld tdi %#p tdu %#p data %#p\n",
+		iso->td0frno, iso->tdi, iso->tdu, iso->data);
+	if(iso->err != nil)
+		print("\terr %s\n", iso->err);
+	if(iso->err != nil)
+		print("\terr='%s'\n", iso->err);
+	if(all == 0)
+		if(iso->hs != 0){
+			tdi = iso->tdi;
+			seprintitd(buf, buf+sizeof(buf), tdi);
+			print("\ttdi %s\n", buf);
+			tdu = iso->tdu;
+			seprintitd(buf, buf+sizeof(buf), tdu);
+			print("\ttdu %s\n", buf);
+		}else{
+			stdi = iso->stdi;
+			seprintsitd(buf, buf+sizeof(buf), stdi);
+			print("\tstdi %s\n", buf);
+			stdu = iso->stdu;
+			seprintsitd(buf, buf+sizeof(buf), stdu);
+			print("\tstdu %s\n", buf);
+		}
+	else{
+		for(i = 0; i < Nisoframes; i++)
+			if(iso->tdps[i] != nil)
+			if(iso->hs != 0){
+				td = iso->itdps[i];
+				seprintitd(buf, buf+sizeof(buf), td);
+				if(td == iso->tdi)
+					print("i->");
+				if(td == iso->tdu)
+					print("i->");
+				print("[%d]\t%s", i, buf);
+			}else{
+				std = iso->sitdps[i];
+				seprintsitd(buf, buf+sizeof(buf), std);
+				if(std == iso->stdi)
+					print("i->");
+				if(std == iso->stdu)
+					print("u->");
+				print("[%d]\t%s", i, buf);
+			}
+	}
+}
+
+static void
+dump(Hci *hp)
+{
+	Ctlr *ctlr;
+	Isoio *iso;
+	Eopio *opio;
+	int i;
+	char buf[128];
+	char *s, *se;
+	Qh *qh;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	ilock(ctlr);
+	print("ehci port %#p frames %#p (%d fr.) nintr %d ntdintr %d",
+		ctlr->capio, ctlr->frames, ctlr->nframes,
+		ctlr->nintr, ctlr->ntdintr);
+	print(" nqhintr %d nisointr %d\n", ctlr->nqhintr, ctlr->nisointr);
+	print("\tcmd %#ulx sts %#ulx intr %#ulx frno %uld",
+		opio->cmd, opio->sts, opio->intr, opio->frno);
+	print(" base %#ulx link %#ulx fr0 %#ulx\n",
+		opio->frbase, opio->link, ctlr->frames[0]);
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "\t");
+	for(i = 0; i < hp->nports; i++){
+		s = seprint(s, se, "p%d %#ulx ", i, opio->portsc[i]);
+		if(hp->nports > 4 && i == hp->nports/2 - 1)
+			s = seprint(s, se, "\n\t");
+	}
+	print("%s\n", buf);
+	qh = ctlr->qhs;
+	i = 0;
+	do{
+		qhdump(qh);
+		qh = qh->next;
+	}while(qh != ctlr->qhs && i++ < 100);
+	if(i > 100)
+		print("...too many Qhs...\n");
+	if(ctlr->intrqhs != nil)
+		print("intr qhs:\n");
+	for(qh = ctlr->intrqhs; qh != nil; qh = qh->inext)
+		qhdump(qh);
+	if(ctlr->iso != nil)
+		print("iso:\n");
+	for(iso = ctlr->iso; iso != nil; iso = iso->next)
+		isodump(ctlr->iso, 0);
+	print("%d eds in tree\n", ctlr->ntree);
+	iunlock(ctlr);
+	lock(&edpool);
+	print("%d eds allocated = %d in use + %d free\n",
+		edpool.nalloc, edpool.ninuse, edpool.nfree);
+	unlock(&edpool);
+}
+
+static char*
+errmsg(int err)
+{
+	if(err == 0)
+		return "ok";
+	if(err & Tddberr)
+		return "data buffer error";
+	if(err & Tdbabble)
+		return "babble detected";
+	if(err & Tdtrerr)
+		return "transaction error";
+	if(err & Tdmmf)
+		return "missed µframe";
+	if(err & Tdhalt)
+		return Estalled;	/* [uo]hci report this error */
+	return Eio;
+}
+
+static char*
+ierrmsg(int err)
+{
+	if(err == 0)
+		return "ok";
+	if(err & Itddberr)
+		return "data buffer error";
+	if(err & Itdbabble)
+		return "babble detected";
+	if(err & Itdtrerr)
+		return "transaction error";
+	return Eio;
+}
+
+static char*
+serrmsg(int err)
+{
+	if(err & Stderr)
+		return "translation translator error";
+	/* other errors have same numbers than Td errors */
+	return errmsg(err);
+}
+
+static int
+isocanread(void *a)
+{
+	Isoio *iso;
+
+	iso = a;
+	if(iso->state == Qclose)
+		return 1;
+	if(iso->state == Qrun && iso->tok == Tdtokin){
+		if(iso->hs != 0 && iso->tdi != iso->tdu)
+			return 1;
+		if(iso->hs == 0 && iso->stdi != iso->stdu)
+			return 1;
+	}
+	return 0;
+}
+
+static int
+isocanwrite(void *a)
+{
+	Isoio *iso;
+
+	iso = a;
+	if(iso->state == Qclose)
+		return 1;
+	if(iso->state == Qrun && iso->tok == Tdtokout){
+		if(iso->hs != 0 && iso->tdu->next != iso->tdi)
+			return 1;
+		if(iso->hs == 0 && iso->stdu->next != iso->stdi)
+			return 1;
+	}
+	return 0;
+}
+
+static void
+itdinit(Isoio *iso, Itd *td)
+{
+	int p, t;
+	ulong pa, size, tsize;
+
+	/*
+	 * BUG: This does not put an integral number of samples
+	 * on each µframe unless samples per packet % 8 == 0
+	 * Also, all samples are packed early on each frame.
+	 */
+	p = 0;
+	size = td->ndata = td->mdata;
+	pa = PADDR(td->data);
+	for(t = 0; size > 0 && t < 8; t++){
+		tsize = size;
+		if(tsize > iso->maxsize)
+			tsize = iso->maxsize;
+		size -= tsize;
+		td->csw[t] = tsize << Itdlenshift;
+		assert(p < nelem(td->buffer));
+		td->csw[t] |= p << Itdpgshift;
+		td->csw[t] |= (pa & 0xFFF) << Itdoffshift;
+		td->csw[t] |= Itdactive|Itdioc;
+		coherence();
+		if(((pa+tsize) & ~0xFFF) != (pa & ~0xFFF))
+			p++;
+		pa += tsize;
+	}
+}
+
+static void
+sitdinit(Isoio *iso, Sitd *td)
+{
+	td->ndata = td->mdata & Stdlenmask;
+	td->csw = (td->ndata << Stdlenshift) | Stdactive | Stdioc;
+	td->buffer[0] = PADDR(td->data);
+	td->buffer[1] = (td->buffer[0] & ~0xFFF) + 0x1000;
+	if(iso->tok == Tdtokin || td->ndata <= 188)
+		td->buffer[1] |= Stdtpall;
+	else
+		td->buffer[1] |= Stdtpbegin;
+	if(iso->tok == Tdtokin)
+		td->buffer[1] |= 1;
+	else
+		td->buffer[1] |= ((td->ndata + 187 ) / 188) & Stdtcntmask;
+	coherence();
+}
+
+static int
+itdactive(Itd *td)
+{
+	int i;
+
+	for(i = 0; i < nelem(td->csw); i++)
+		if((td->csw[i] & Itdactive) != 0)
+			return 1;
+	return 0;
+}
+
+static int
+isohsinterrupt(Ctlr *ctlr, Isoio *iso)
+{
+	int err, i, nframes, t;
+	Itd *tdi;
+
+	tdi = iso->tdi;
+	assert(tdi != nil);
+	if(itdactive(tdi))			/* not all tds are done */
+		return 0;
+	ctlr->nisointr++;
+	ddiprint("isohsintr: iso %#p: tdi %#p tdu %#p\n", iso, tdi, iso->tdu);
+	if(iso->state != Qrun && iso->state != Qdone)
+		panic("isofsintr: iso state");
+	if(debug > 1 || iso->debug > 1)
+		isodump(iso, 0);
+
+	nframes = iso->nframes / 2;		/* limit how many we look */
+	if(nframes > Nisoframes)
+		nframes = Nisoframes;
+
+	if(iso->tok == Tdtokin)
+		tdi->ndata = 0;
+	/* else, it has the number of bytes transferred */
+
+	for(i = 0; i < nframes && itdactive(tdi) == 0; i++){
+		err = 0;
+		if(iso->tok == Tdtokin)
+			tdi->ndata += (tdi->csw[i] >> Itdlenshift)&Itdlenmask;
+		for(t = 0; t < nelem(tdi->csw); t++){
+			tdi->csw[i] &= ~Itdioc;
+			coherence();
+			err |= tdi->csw[i] & Itderrors;
+		}
+		if(err == 0)
+			iso->nerrs = 0;
+		else if(iso->nerrs++ > iso->nframes/2){
+			if(iso->err == nil){
+				iso->err = ierrmsg(err);
+				diprint("isohsintr: tdi %#p error %#ux %s\n",
+					tdi, err, iso->err);
+				diprint("ctlr load %uld\n", ctlr->load);
+			}
+			tdi->ndata = 0;
+		}else
+			tdi->ndata = 0;
+		if(tdi->next == iso->tdu || tdi->next->next == iso->tdu){
+			memset(iso->tdu->data, 0, iso->tdu->mdata);
+			itdinit(iso, iso->tdu);
+			iso->tdu = iso->tdu->next;
+			iso->nleft = 0;
+		}
+		tdi = tdi->next;
+	}
+	ddiprint("isohsintr: %d frames processed\n", nframes);
+	if(i == nframes)
+		tdi->csw[0] |= Itdioc;
+	coherence();
+	iso->tdi = tdi;
+	if(isocanwrite(iso) || isocanread(iso)){
+		diprint("wakeup iso %#p tdi %#p tdu %#p\n", iso,
+			iso->tdi, iso->tdu);
+		wakeup(iso);
+	}
+	return 1;
+}
+
+static int
+isofsinterrupt(Ctlr *ctlr, Isoio *iso)
+{
+	int err, i, nframes;
+	Sitd *stdi;
+
+	stdi = iso->stdi;
+	assert(stdi != nil);
+	if((stdi->csw & Stdactive) != 0)		/* nothing new done */
+		return 0;
+	ctlr->nisointr++;
+	ddiprint("isofsintr: iso %#p: tdi %#p tdu %#p\n", iso, stdi, iso->stdu);
+	if(iso->state != Qrun && iso->state != Qdone)
+		panic("isofsintr: iso state");
+	if(debug > 1 || iso->debug > 1)
+		isodump(iso, 0);
+
+	nframes = iso->nframes / 2;		/* limit how many we look */
+	if(nframes > Nisoframes)
+		nframes = Nisoframes;
+
+	for(i = 0; i < nframes && (stdi->csw & Stdactive) == 0; i++){
+		stdi->csw &= ~Stdioc;
+		coherence();
+		err = stdi->csw & Stderrors;
+		if(err == 0){
+			iso->nerrs = 0;
+			if(iso->tok == Tdtokin)
+				stdi->ndata = (stdi->csw>>Stdlenshift)&Stdlenmask;
+			/* else len is assumed correct */
+		}else if(iso->nerrs++ > iso->nframes/2){
+			if(iso->err == nil){
+				iso->err = serrmsg(err);
+				diprint("isofsintr: tdi %#p error %#ux %s\n",
+					stdi, err, iso->err);
+				diprint("ctlr load %uld\n", ctlr->load);
+			}
+			stdi->ndata = 0;
+		}else
+			stdi->ndata = 0;
+
+		if(stdi->next == iso->stdu || stdi->next->next == iso->stdu){
+			memset(iso->stdu->data, 0, iso->stdu->mdata);
+			sitdinit(iso, iso->stdu);
+			iso->stdu = iso->stdu->next;
+			iso->nleft = 0;
+		}
+		stdi = stdi->next;
+	}
+	ddiprint("isofsintr: %d frames processed\n", nframes);
+	if(i == nframes)
+		stdi->csw |= Stdioc;
+	coherence();
+	iso->stdi = stdi;
+	if(isocanwrite(iso) || isocanread(iso)){
+		diprint("wakeup iso %#p tdi %#p tdu %#p\n", iso,
+			iso->stdi, iso->stdu);
+		wakeup(iso);
+	}
+	return 1;
+}
+
+static int
+qhinterrupt(Ctlr *ctlr, Qh *qh)
+{
+	Td *td;
+	int err;
+
+	if(qh->state != Qrun)
+		panic("qhinterrupt: qh state");
+	if(qh->tds == nil)
+		panic("qhinterrupt: no tds");
+	if((qh->tds->csw & Tdactive) == 0)
+		ddqprint("qhinterrupt port %#p qh %#p\n",ctlr->capio, qh);
+	for(td = qh->tds; td != nil; td = td->next){
+		if(td->csw & Tdactive)
+			return 0;
+		if((td->csw & Tderrors) != 0){
+			err = td->csw & Tderrors;
+			if(qh->io->err == nil){
+				qh->io->err = errmsg(td->csw & Tderrors);
+				dqprint("qhintr: td %#p csw %#ulx error %#ux %s\n",
+					td, td->csw, err, qh->io->err);
+			}
+			break;
+		}
+		td->ndata = tdlen(td);
+		if(td->ndata < maxtdlen(td)){	/* EOT */
+			td = td->next;
+			break;
+		}
+	}
+	/*
+	 * Done. Make void the Tds not used (errors or EOT) and wakeup epio.
+	 */
+	for(; td != nil; td = td->next)
+		td->ndata = 0;
+	qh->state = Qdone;
+	wakeup(qh->io);
+	return 1;
+}
+
+static int
+ehciintr(Hci *hp)
+{
+	Ctlr *ctlr;
+	Eopio *opio;
+	Isoio *iso;
+	ulong sts;
+	Qh *qh;
+	int i, some;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+
+	/*
+	 * Will we know in USB 3.0 who the interrupt was for?.
+	 * Do they still teach indexing in CS?
+	 * This is Intel's doing.
+	 */
+	ilock(ctlr);
+	ctlr->nintr++;
+	sts = opio->sts & Sintrs;
+	if(sts == 0){		/* not ours; shared intr. */
+		iunlock(ctlr);
+		return 0;
+	}
+	opio->sts = sts;
+	coherence();
+	if((sts & Sherr) != 0)
+		print("ehci: port %#p fatal host system error\n", ctlr->capio);
+	if((sts & Shalted) != 0)
+		print("ehci: port %#p: halted\n", ctlr->capio);
+	if((sts & Sasync) != 0){
+		dprint("ehci: doorbell\n");
+		wakeup(ctlr);
+	}
+	/*
+	 * We enter always this if, even if it seems the
+	 * interrupt does not report anything done/failed.
+	 * Some controllers don't post interrupts right.
+	 */
+	some = 0;
+	if((sts & (Serrintr|Sintr)) != 0){
+		ctlr->ntdintr++;
+		if(debug > 1){
+			print("ehci port %#p frames %#p nintr %d ntdintr %d",
+				ctlr->capio, ctlr->frames,
+				ctlr->nintr, ctlr->ntdintr);
+			print(" nqhintr %d nisointr %d\n",
+				ctlr->nqhintr, ctlr->nisointr);
+			print("\tcmd %#ulx sts %#ulx intr %#ulx frno %uld",
+				opio->cmd, opio->sts, opio->intr, opio->frno);
+		}
+
+		/* process the Iso transfers */
+		for(iso = ctlr->iso; iso != nil; iso = iso->next)
+			if(iso->state == Qrun || iso->state == Qdone)
+				if(iso->hs != 0)
+					some += isohsinterrupt(ctlr, iso);
+				else
+					some += isofsinterrupt(ctlr, iso);
+
+		/* process the qhs in the periodic tree */
+		for(qh = ctlr->intrqhs; qh != nil; qh = qh->inext)
+			if(qh->state == Qrun)
+				some += qhinterrupt(ctlr, qh);
+
+		/* process the async Qh circular list */
+		qh = ctlr->qhs;
+		i = 0;
+		do{
+			if(qh->state == Qrun)
+				some += qhinterrupt(ctlr, qh);
+			qh = qh->next;
+		}while(qh != ctlr->qhs && i++ < 100);
+		if(i > 100)
+			print("echi: interrupt: qh loop?\n");
+	}
+	iunlock(ctlr);
+	return some;
+}
+
+static void
+interrupt(Ureg*, void* a)
+{
+	ehciintr(a);
+}
+
+static int
+portenable(Hci *hp, int port, int on)
+{
+	Ctlr *ctlr;
+	Eopio *opio;
+	int s;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	s = opio->portsc[port-1];
+	qlock(&ctlr->portlck);
+	if(waserror()){
+		qunlock(&ctlr->portlck);
+		nexterror();
+	}
+	dprint("ehci %#p port %d enable=%d; sts %#x\n",
+		ctlr->capio, port, on, s);
+	ilock(ctlr);
+	if(s & (Psstatuschg | Pschange))
+		opio->portsc[port-1] = s;
+	if(on)
+		opio->portsc[port-1] |= Psenable;
+	else
+		opio->portsc[port-1] &= ~Psenable;
+	coherence();
+	microdelay(64);
+	iunlock(ctlr);
+	tsleep(&up->sleep, return0, 0, Enabledelay);
+	dprint("ehci %#p port %d enable=%d: sts %#ulx\n",
+		ctlr->capio, port, on, opio->portsc[port-1]);
+	qunlock(&ctlr->portlck);
+	poperror();
+	return 0;
+}
+
+/*
+ * If we detect during status that the port is low-speed or
+ * during reset that it's full-speed, the device is not for
+ * ourselves. The companion controller will take care.
+ * Low-speed devices will not be seen by usbd. Full-speed
+ * ones are seen because it's only after reset that we know what
+ * they are (usbd may notice a device not enabled in this case).
+ */
+static void
+portlend(Ctlr *ctlr, int port, char *ss)
+{
+	Eopio *opio;
+	ulong s;
+
+	opio = ctlr->opio;
+
+	dprint("ehci %#p port %d: %s speed device: no longer owned\n",
+		ctlr->capio, port, ss);
+	s = opio->portsc[port-1] & ~(Pschange|Psstatuschg);
+	opio->portsc[port-1] = s | Psowner;
+	coherence();
+}
+
+static int
+portreset(Hci *hp, int port, int on)
+{
+	ulong s;
+	Eopio *opio;
+	Ctlr *ctlr;
+	int i;
+
+	if(on == 0)
+		return 0;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	qlock(&ctlr->portlck);
+	if(waserror()){
+		iunlock(ctlr);
+		qunlock(&ctlr->portlck);
+		nexterror();
+	}
+	s = opio->portsc[port-1];
+	dprint("ehci %#p port %d reset; sts %#ulx\n", ctlr->capio, port, s);
+	ilock(ctlr);
+	s &= ~(Psenable|Psreset);
+	opio->portsc[port-1] = s|Psreset;
+	coherence();
+	for(i = 0; i < 10; i++){
+		delay(10);
+		if((opio->portsc[port-1] & Psreset) == 0)
+			break;
+	}
+	opio->portsc[port-1] &= ~Psreset;
+	coherence();
+	delay(10);
+	if((opio->portsc[port-1] & Psenable) == 0)
+		portlend(ctlr, port, "full");
+
+	iunlock(ctlr);
+	dprint("ehci %#p after port %d reset; sts %#ulx\n",
+		ctlr->capio, port, opio->portsc[port-1]);
+	qunlock(&ctlr->portlck);
+	poperror();
+	return 0;
+}
+
+static int
+portstatus(Hci *hp, int port)
+{
+	int s, r;
+	Eopio *opio;
+	Ctlr *ctlr;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	qlock(&ctlr->portlck);
+	if(waserror()){
+		iunlock(ctlr);
+		qunlock(&ctlr->portlck);
+		nexterror();
+	}
+	ilock(ctlr);
+	s = opio->portsc[port-1];
+	if(s & (Psstatuschg | Pschange)){
+		opio->portsc[port-1] = s;
+		coherence();
+		ddprint("ehci %#p port %d status %#x\n", ctlr->capio, port, s);
+	}
+	/*
+	 * If the port is a low speed port we yield ownership now
+	 * to the [uo]hci companion controller and pretend it's not here.
+	 */
+	if((s & Pspresent) != 0 && (s & Pslinemask) == Pslow){
+		portlend(ctlr, port, "low");
+		s &= ~Pspresent;		/* not for us this time */
+	}
+	iunlock(ctlr);
+	qunlock(&ctlr->portlck);
+	poperror();
+
+	/*
+	 * We must return status bits as a
+	 * get port status hub request would do.
+	 */
+	r = 0;
+	if(s & Pspresent)
+		r |= HPpresent|HPhigh;
+	if(s & Psenable)
+		r |= HPenable;
+	if(s & Pssuspend)
+		r |= HPsuspend;
+	if(s & Psreset)
+		r |= HPreset;
+	if(s & Psstatuschg)
+		r |= HPstatuschg;
+	if(s & Pschange)
+		r |= HPchange;
+	return r;
+}
+
+static char*
+seprintio(char *s, char *e, Qio *io, char *pref)
+{
+	s = seprint(s,e,"%s io %#p qh %#p id %#x", pref, io, io->qh, io->usbid);
+	s = seprint(s,e," iot %ld", io->iotime);
+	s = seprint(s,e," tog %#x tok %#x err %s", io->toggle, io->tok, io->err);
+	return s;
+}
+
+static char*
+seprintep(char *s, char *e, Ep *ep)
+{
+	Qio *io;
+	Ctlio *cio;
+	Ctlr *ctlr;
+
+	ctlr = ep->hp->aux;
+	ilock(ctlr);
+	if(ep->aux == nil){
+		*s = 0;
+		iunlock(ctlr);
+		return s;
+	}
+	switch(ep->ttype){
+	case Tctl:
+		cio = ep->aux;
+		s = seprintio(s, e, cio, "c");
+		s = seprint(s, e, "\trepl %d ndata %d\n", ep->rhrepl, cio->ndata);
+		break;
+	case Tbulk:
+	case Tintr:
+		io = ep->aux;
+		if(ep->mode != OWRITE)
+			s = seprintio(s, e, &io[OREAD], "r");
+		if(ep->mode != OREAD)
+			s = seprintio(s, e, &io[OWRITE], "w");
+		break;
+	case Tiso:
+		*s = 0;
+		break;
+	}
+	iunlock(ctlr);
+	return s;
+}
+
+/*
+ * halt condition was cleared on the endpoint. update our toggles.
+ */
+static void
+clrhalt(Ep *ep)
+{
+	Qio *io;
+
+	ep->clrhalt = 0;
+	switch(ep->ttype){
+	case Tintr:
+	case Tbulk:
+		io = ep->aux;
+		if(ep->mode != OREAD){
+			qlock(&io[OWRITE]);
+			io[OWRITE].toggle = Tddata0;
+			deprint("ep clrhalt for io %#p\n", io+OWRITE);
+			qunlock(&io[OWRITE]);
+		}
+		if(ep->mode != OWRITE){
+			qlock(&io[OREAD]);
+			io[OREAD].toggle = Tddata0;
+			deprint("ep clrhalt for io %#p\n", io+OREAD);
+			qunlock(&io[OREAD]);
+		}
+		break;
+	}
+}
+
+static void
+xdump(char* pref, void *qh)
+{
+	int i;
+	ulong *u;
+
+	u = qh;
+	print("%s %#p:", pref, u);
+	for(i = 0; i < 16; i++)
+		if((i%4) == 0)
+			print("\n %#8.8ulx", u[i]);
+		else
+			print(" %#8.8ulx", u[i]);
+	print("\n");
+}
+
+static long
+episohscpy(Ctlr *ctlr, Ep *ep, Isoio* iso, uchar *b, long count)
+{
+	int nr;
+	long tot;
+	Itd *tdu;
+
+	for(tot = 0; iso->tdi != iso->tdu && tot < count; tot += nr){
+		tdu = iso->tdu;
+		if(itdactive(tdu))
+			break;
+		nr = tdu->ndata;
+		if(tot + nr > count)
+			nr = count - tot;
+		if(nr == 0)
+			print("ehci: ep%d.%d: too many polls\n",
+				ep->dev->nb, ep->nb);
+		else{
+			iunlock(ctlr);		/* We could page fault here */
+			memmove(b+tot, tdu->data, nr);
+			ilock(ctlr);
+			if(nr < tdu->ndata)
+				memmove(tdu->data, tdu->data+nr, tdu->ndata - nr);
+			tdu->ndata -= nr;
+		}
+		if(tdu->ndata == 0){
+			itdinit(iso, tdu);
+			iso->tdu = tdu->next;
+		}
+	}
+	return tot;
+}
+
+static long
+episofscpy(Ctlr *ctlr, Ep *ep, Isoio* iso, uchar *b, long count)
+{
+	int nr;
+	long tot;
+	Sitd *stdu;
+
+	for(tot = 0; iso->stdi != iso->stdu && tot < count; tot += nr){
+		stdu = iso->stdu;
+		if(stdu->csw & Stdactive){
+			diprint("ehci: episoread: %#p tdu active\n", iso);
+			break;
+		}
+		nr = stdu->ndata;
+		if(tot + nr > count)
+			nr = count - tot;
+		if(nr == 0)
+			print("ehci: ep%d.%d: too many polls\n",
+				ep->dev->nb, ep->nb);
+		else{
+			iunlock(ctlr);		/* We could page fault here */
+			memmove(b+tot, stdu->data, nr);
+			ilock(ctlr);
+			if(nr < stdu->ndata)
+				memmove(stdu->data,stdu->data+nr,stdu->ndata - nr);
+			stdu->ndata -= nr;
+		}
+		if(stdu->ndata == 0){
+			sitdinit(iso, stdu);
+			iso->stdu = stdu->next;
+		}
+	}
+	return tot;
+}
+
+static long
+episoread(Ep *ep, Isoio *iso, void *a, long count)
+{
+	Ctlr *ctlr;
+	uchar *b;
+	long tot;
+
+	iso->debug = ep->debug;
+	diprint("ehci: episoread: %#p ep%d.%d\n", iso, ep->dev->nb, ep->nb);
+
+	b = a;
+	ctlr = ep->hp->aux;
+	qlock(iso);
+	if(waserror()){
+		qunlock(iso);
+		nexterror();
+	}
+	iso->err = nil;
+	iso->nerrs = 0;
+	ilock(ctlr);
+	if(iso->state == Qclose){
+		iunlock(ctlr);
+		error(iso->err ? iso->err : Eio);
+	}
+	iso->state = Qrun;
+	while(isocanread(iso) == 0){
+		iunlock(ctlr);
+		diprint("ehci: episoread: %#p sleep\n", iso);
+		if(waserror()){
+			if(iso->err == nil)
+				iso->err = "I/O timed out";
+			ilock(ctlr);
+			break;
+		}
+		tsleep(iso, isocanread, iso, ep->tmout);
+		poperror();
+		ilock(ctlr);
+	}
+	if(iso->state == Qclose){
+		iunlock(ctlr);
+		error(iso->err ? iso->err : Eio);
+	}
+	iso->state = Qdone;
+	assert(iso->tdu != iso->tdi);
+
+	if(iso->hs != 0)
+		tot = episohscpy(ctlr, ep, iso, b, count);
+	else
+		tot = episofscpy(ctlr, ep, iso, b, count);
+	iunlock(ctlr);
+	qunlock(iso);
+	poperror();
+	diprint("uhci: episoread: %#p %uld bytes err '%s'\n", iso, tot, iso->err);
+	if(iso->err != nil)
+		error(iso->err);
+	return tot;
+}
+
+/*
+ * iso->tdu is the next place to put data. When it gets full
+ * it is activated and tdu advanced.
+ */
+static long
+putsamples(Isoio *iso, uchar *b, long count)
+{
+	long tot, n;
+
+	for(tot = 0; isocanwrite(iso) && tot < count; tot += n){
+		n = count-tot;
+		if(iso->hs != 0){
+			if(n > iso->tdu->mdata - iso->nleft)
+				n = iso->tdu->mdata - iso->nleft;
+			memmove(iso->tdu->data+iso->nleft, b+tot, n);
+			iso->nleft += n;
+			if(iso->nleft == iso->tdu->mdata){
+				itdinit(iso, iso->tdu);
+				iso->nleft = 0;
+				iso->tdu = iso->tdu->next;
+			}
+		}else{
+			if(n > iso->stdu->mdata - iso->nleft)
+				n = iso->stdu->mdata - iso->nleft;
+			memmove(iso->stdu->data+iso->nleft, b+tot, n);
+			iso->nleft += n;
+			if(iso->nleft == iso->stdu->mdata){
+				sitdinit(iso, iso->stdu);
+				iso->nleft = 0;
+				iso->stdu = iso->stdu->next;
+			}
+		}
+	}
+	return tot;
+}
+
+/*
+ * Queue data for writing and return error status from
+ * last writes done, to maintain buffered data.
+ */
+static long
+episowrite(Ep *ep, Isoio *iso, void *a, long count)
+{
+	Ctlr *ctlr;
+	uchar *b;
+	int tot, nw;
+	char *err;
+
+	iso->debug = ep->debug;
+	diprint("ehci: episowrite: %#p ep%d.%d\n", iso, ep->dev->nb, ep->nb);
+
+	ctlr = ep->hp->aux;
+	qlock(iso);
+	if(waserror()){
+		qunlock(iso);
+		nexterror();
+	}
+	ilock(ctlr);
+	if(iso->state == Qclose){
+		iunlock(ctlr);
+		error(iso->err ? iso->err : Eio);
+	}
+	iso->state = Qrun;
+	b = a;
+	for(tot = 0; tot < count; tot += nw){
+		while(isocanwrite(iso) == 0){
+			iunlock(ctlr);
+			diprint("ehci: episowrite: %#p sleep\n", iso);
+			if(waserror()){
+				if(iso->err == nil)
+					iso->err = "I/O timed out";
+				ilock(ctlr);
+				break;
+			}
+			tsleep(iso, isocanwrite, iso, ep->tmout);
+			poperror();
+			ilock(ctlr);
+		}
+		err = iso->err;
+		iso->err = nil;
+		if(iso->state == Qclose || err != nil){
+			iunlock(ctlr);
+			error(err ? err : Eio);
+		}
+		if(iso->state != Qrun)
+			panic("episowrite: iso not running");
+		iunlock(ctlr);		/* We could page fault here */
+		nw = putsamples(iso, b+tot, count-tot);
+		ilock(ctlr);
+	}
+	if(iso->state != Qclose)
+		iso->state = Qdone;
+	iunlock(ctlr);
+	err = iso->err;		/* in case it failed early */
+	iso->err = nil;
+	qunlock(iso);
+	poperror();
+	if(err != nil)
+		error(err);
+	diprint("ehci: episowrite: %#p %d bytes\n", iso, tot);
+	return tot;
+}
+
+static int
+nexttoggle(int toggle, int count, int maxpkt)
+{
+	int np;
+
+	np = count / maxpkt;
+	if(np == 0)
+		np = 1;
+	if((np % 2) == 0)
+		return toggle;
+	if(toggle == Tddata1)
+		return Tddata0;
+	else
+		return Tddata1;
+}
+
+static Td*
+epgettd(Qio *io, int flags, void *a, int count, int maxpkt)
+{
+	Td *td;
+	ulong pa;
+	int i;
+
+	if(count > Tdmaxpkt)
+		panic("ehci: epgettd: too many bytes");
+	td = tdalloc();
+	td->csw = flags;
+	td->csw |= io->toggle | io->tok | (count << Tdlenshift);
+	td->csw |= Tderr2|Tderr1;
+
+	/*
+	 * use the space wasted by alignment as an
+	 * embedded buffer if count bytes fit in there.
+	 */
+	assert(Align > sizeof(Td));
+	if(count <= Align - sizeof(Td))
+		td->data = td->sbuff;
+	else
+		td->data = td->buff = smalloc(Tdmaxpkt);
+
+	pa = PADDR(td->data);
+	for(i = 0; i < nelem(td->buffer); i++){
+		td->buffer[i] = pa;
+		if(i > 0)
+			td->buffer[i] &= ~0xFFF;
+		pa += 0x1000;
+	}
+	td->ndata = count;
+	if(a != nil && count > 0)
+		memmove(td->data, a, count);
+	io->toggle = nexttoggle(io->toggle, count, maxpkt);
+	return td;
+}
+
+/*
+ * Try to get them idle
+ */
+static void
+aborttds(Qh *qh)
+{
+	Td *td;
+
+	qh->state = Qdone;
+	if(qh->sched >= 0 && (qh->eps0&Qhspeedmask) != Qhhigh)
+		qh->eps0 |= Qhint;	/* inactivate on next pass */
+	coherence();
+	for(td = qh->tds; td != nil; td = td->next){
+		if(td->csw & Tdactive)
+			td->ndata = 0;
+		td->csw |= Tdhalt;
+	}
+}
+
+/*
+ * Some controllers do not post the usb/error interrupt after
+ * the work has been done. It seems that we must poll for them.
+ */
+static int
+workpending(void *a)
+{
+	Ctlr *ctlr;
+
+	ctlr = a;
+	return ctlr->nreqs > 0;
+}
+
+static void
+ehcipoll(void* a)
+{
+	Hci *hp;
+	Ctlr *ctlr;
+	Poll *poll;
+	int i;
+
+	hp = a;
+	ctlr = hp->aux;
+	poll = &ctlr->poll;
+	for(;;){
+		if(ctlr->nreqs == 0){
+			if(0)ddprint("ehcipoll %#p sleep\n", ctlr->capio);
+			sleep(poll, workpending, ctlr);
+			if(0)ddprint("ehcipoll %#p awaken\n", ctlr->capio);
+		}
+		for(i = 0; i < 16 && ctlr->nreqs > 0; i++)
+			if(ehciintr(hp) == 0)
+				 break;
+		do{
+			tsleep(&up->sleep, return0, 0, 1);
+			ehciintr(hp);
+		}while(ctlr->nreqs > 0);
+	}
+}
+
+static void
+pollcheck(Hci *hp)
+{
+	Ctlr *ctlr;
+	Poll *poll;
+
+	ctlr = hp->aux;
+	poll = &ctlr->poll;
+
+	if(poll->must != 0 && poll->does == 0){
+		lock(poll);
+		if(poll->must != 0 && poll->does == 0){
+			poll->does++;
+			print("ehci %#p: polling\n", ctlr->capio);
+			kproc("ehcipoll", ehcipoll, hp);
+		}
+		unlock(poll);
+	}
+}
+
+static int
+epiodone(void *a)
+{
+	Qh *qh;
+
+	qh = a;
+	return qh->state != Qrun;
+}
+
+static void
+epiowait(Hci *hp, Qio *io, int tmout, ulong load)
+{
+	Qh *qh;
+	int timedout;
+	Ctlr *ctlr;
+
+	ctlr = hp->aux;
+	qh = io->qh;
+	ddqprint("ehci io %#p sleep on qh %#p state %s\n",
+		io, qh, qhsname[qh->state]);
+	timedout = 0;
+	if(waserror()){
+		dqprint("ehci io %#p qh %#p timed out\n", io, qh);
+		timedout++;
+	}else{
+		if(tmout == 0)
+			sleep(io, epiodone, qh);
+		else
+			tsleep(io, epiodone, qh, tmout);
+		poperror();
+	}
+
+	ilock(ctlr);
+	/* Are we missing interrupts? */
+	if(qh->state == Qrun){
+		iunlock(ctlr);
+		ehciintr(hp);
+		ilock(ctlr);
+		if(qh->state == Qdone){
+			dqprint("ehci %#p: polling required\n", ctlr->capio);
+			ctlr->poll.must = 1;
+			pollcheck(hp);
+		}
+	}
+
+	if(qh->state == Qrun){
+		dqprint("ehci io %#p qh %#p timed out (no intr?)\n", io, qh);
+		timedout = 1;
+	}else if(qh->state != Qdone && qh->state != Qclose)
+		panic("ehci: epio: queue state %d", qh->state);
+	if(timedout){
+		aborttds(io->qh);
+		io->err = "request timed out";
+		iunlock(ctlr);
+		if(!waserror()){
+			tsleep(&up->sleep, return0, 0, Abortdelay);
+			poperror();
+		}
+		ilock(ctlr);
+	}
+	if(qh->state != Qclose)
+		qh->state = Qidle;
+	qhlinktd(qh, nil);
+	ctlr->load -= load;
+	ctlr->nreqs--;
+	iunlock(ctlr);
+}
+
+/*
+ * Non iso I/O.
+ * To make it work for control transfers, the caller may
+ * lock the Qio for the entire control transfer.
+ */
+static long
+epio(Ep *ep, Qio *io, void *a, long count, int mustlock)
+{
+	Td *td, *ltd, *td0, *ntd;
+	Ctlr *ctlr;
+	Qh* qh;
+	long n, tot;
+	char buf[128];
+	uchar *c;
+	int saved, ntds, tmout;
+	ulong load;
+	char *err;
+
+	qh = io->qh;
+	ctlr = ep->hp->aux;
+	io->debug = ep->debug;
+	tmout = ep->tmout;
+	ddeprint("epio: %s ep%d.%d io %#p count %ld load %uld\n",
+		io->tok == Tdtokin ? "in" : "out",
+		ep->dev->nb, ep->nb, io, count, ctlr->load);
+	if((debug > 1 || ep->debug > 1) && io->tok != Tdtokin){
+		seprintdata(buf, buf+sizeof(buf), a, count);
+		print("echi epio: user data: %s\n", buf);
+	}
+	if(mustlock){
+		qlock(io);
+		if(waserror()){
+			qunlock(io);
+			nexterror();
+		}
+	}
+	io->err = nil;
+	ilock(ctlr);
+	if(qh->state == Qclose){	/* Tds released by cancelio */
+		iunlock(ctlr);
+		error(io->err ? io->err : Eio);
+	}
+	if(qh->state != Qidle)
+		panic("epio: qh not idle");
+	qh->state = Qinstall;
+	iunlock(ctlr);
+
+	c = a;
+	td0 = ltd = nil;
+	load = tot = 0;
+	do{
+		n = (Tdmaxpkt / ep->maxpkt) * ep->maxpkt;
+		if(count-tot < n)
+			n = count-tot;
+		if(io->tok != Tdtokin)
+			td = epgettd(io, Tdactive, c+tot, n, ep->maxpkt);
+		else
+			td = epgettd(io, Tdactive, nil, n, ep->maxpkt);
+		if(td0 == nil)
+			td0 = td;
+		else
+			tdlinktd(ltd, td);
+		ltd = td;
+		tot += n;
+		load += ep->load;
+	}while(tot < count);
+	if(td0 == nil || ltd == nil)
+		panic("epio: no td");
+
+	ltd->csw |= Tdioc;	/* the last one interrupts */
+	coherence();
+
+	ddeprint("ehci: load %uld ctlr load %uld\n", load, ctlr->load);
+	if(debug > 1 || ep->debug > 1)
+		dumptd(td0, "epio: put: ");
+
+	ilock(ctlr);
+	if(qh->state != Qclose){
+		io->iotime = TK2MS(MACHP(0)->ticks);
+		qh->state = Qrun;
+		qhlinktd(qh, td0);
+		ctlr->nreqs++;
+		ctlr->load += load;
+	}
+	iunlock(ctlr);
+
+	if(ctlr->poll.does)
+		wakeup(&ctlr->poll);
+
+	epiowait(ep->hp, io, tmout, load);
+	if(debug > 1 || ep->debug > 1){
+		dumptd(td0, "epio: got: ");
+		qhdump(qh);
+	}
+
+	tot = 0;
+	c = a;
+	saved = 0;
+	ntds = 0;
+	for(td = td0; td != nil; td = ntd){
+		ntds++;
+		/*
+		 * Use td tok, not io tok, because of setup packets.
+		 * Also, if the Td was stalled or active (previous Td
+		 * was a short packet), we must save the toggle as it is.
+		 */
+		if(td->csw & (Tdhalt|Tdactive)){
+			if(saved++ == 0)
+				io->toggle = td->csw & Tddata1;
+		}else{
+			tot += td->ndata;
+			if((td->csw & Tdtok) == Tdtokin && td->ndata > 0){
+				memmove(c, td->data, td->ndata);
+				c += td->ndata;
+			}
+		}
+		ntd = td->next;
+		tdfree(td);
+	}
+	err = io->err;
+	if(mustlock){
+		qunlock(io);
+		poperror();
+	}
+	ddeprint("epio: io %#p: %d tds: return %ld err '%s'\n",
+		io, ntds, tot, err);
+	if(err == Estalled)
+		return 0;	/* that's our convention */
+	if(err != nil)
+		error(err);
+	if(tot < 0)
+		error(Eio);
+	return tot;
+}
+
+static long
+epread(Ep *ep, void *a, long count)
+{
+	Ctlio *cio;
+	Qio *io;
+	Isoio *iso;
+	char buf[160];
+	ulong delta;
+
+	ddeprint("ehci: epread\n");
+	if(ep->aux == nil)
+		panic("epread: not open");
+
+	pollcheck(ep->hp);
+
+	switch(ep->ttype){
+	case Tctl:
+		cio = ep->aux;
+		qlock(cio);
+		if(waserror()){
+			qunlock(cio);
+			nexterror();
+		}
+		ddeprint("epread ctl ndata %d\n", cio->ndata);
+		if(cio->ndata < 0)
+			error("request expected");
+		else if(cio->ndata == 0){
+			cio->ndata = -1;
+			count = 0;
+		}else{
+			if(count > cio->ndata)
+				count = cio->ndata;
+			if(count > 0)
+				memmove(a, cio->data, count);
+			/* BUG for big transfers */
+			free(cio->data);
+			cio->data = nil;
+			cio->ndata = 0;	/* signal EOF next time */
+		}
+		qunlock(cio);
+		poperror();
+		if(debug>1 || ep->debug){
+			seprintdata(buf, buf+sizeof(buf), a, count);
+			print("epread: %s\n", buf);
+		}
+		return count;
+	case Tbulk:
+		io = ep->aux;
+		if(ep->clrhalt)
+			clrhalt(ep);
+		return epio(ep, &io[OREAD], a, count, 1);
+	case Tintr:
+		io = ep->aux;
+		delta = TK2MS(MACHP(0)->ticks) - io[OREAD].iotime + 1;
+		if(delta < ep->pollival / 2)
+			tsleep(&up->sleep, return0, 0, ep->pollival/2 - delta);
+		if(ep->clrhalt)
+			clrhalt(ep);
+		return epio(ep, &io[OREAD], a, count, 1);
+	case Tiso:
+		iso = ep->aux;
+		return episoread(ep, iso, a, count);
+	}
+	return -1;
+}
+
+/*
+ * Control transfers are one setup write (data0)
+ * plus zero or more reads/writes (data1, data0, ...)
+ * plus a final write/read with data1 to ack.
+ * For both host to device and device to host we perform
+ * the entire transfer when the user writes the request,
+ * and keep any data read from the device for a later read.
+ * We call epio three times instead of placing all Tds at
+ * the same time because doing so leads to crc/tmout errors
+ * for some devices.
+ * Upon errors on the data phase we must still run the status
+ * phase or the device may cease responding in the future.
+ */
+static long
+epctlio(Ep *ep, Ctlio *cio, void *a, long count)
+{
+	uchar *c;
+	long len;
+
+	ddeprint("epctlio: cio %#p ep%d.%d count %ld\n",
+		cio, ep->dev->nb, ep->nb, count);
+	if(count < Rsetuplen)
+		error("short usb comand");
+	qlock(cio);
+	free(cio->data);
+	cio->data = nil;
+	cio->ndata = 0;
+	if(waserror()){
+		qunlock(cio);
+		free(cio->data);
+		cio->data = nil;
+		cio->ndata = 0;
+		nexterror();
+	}
+
+	/* set the address if unset and out of configuration state */
+	if(ep->dev->state != Dconfig && ep->dev->state != Dreset)
+		if(cio->usbid == 0){
+			cio->usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb&Devmax);
+			qhsetaddr(cio->qh, cio->usbid);
+		}
+	/* adjust maxpkt if the user has learned a different one */
+	if(qhmaxpkt(cio->qh) != ep->maxpkt)
+		qhsetmaxpkt(cio->qh, ep->maxpkt);
+	c = a;
+	cio->tok = Tdtoksetup;
+	cio->toggle = Tddata0;
+	if(epio(ep, cio, a, Rsetuplen, 0) < Rsetuplen)
+		error(Eio);
+	a = c + Rsetuplen;
+	count -= Rsetuplen;
+
+	cio->toggle = Tddata1;
+	if(c[Rtype] & Rd2h){
+		cio->tok = Tdtokin;
+		len = GET2(c+Rcount);
+		if(len <= 0)
+			error("bad length in d2h request");
+		if(len > Maxctllen)
+			error("d2h data too large to fit in ehci");
+		a = cio->data = smalloc(len+1);
+	}else{
+		cio->tok = Tdtokout;
+		len = count;
+	}
+	if(len > 0)
+		if(waserror())
+			len = -1;
+		else{
+			len = epio(ep, cio, a, len, 0);
+			poperror();
+		}
+	if(c[Rtype] & Rd2h){
+		count = Rsetuplen;
+		cio->ndata = len;
+		cio->tok = Tdtokout;
+	}else{
+		if(len < 0)
+			count = -1;
+		else
+			count = Rsetuplen + len;
+		cio->tok = Tdtokin;
+	}
+	cio->toggle = Tddata1;
+	epio(ep, cio, nil, 0, 0);
+	qunlock(cio);
+	poperror();
+	ddeprint("epctlio cio %#p return %ld\n", cio, count);
+	return count;
+}
+
+static long
+epwrite(Ep *ep, void *a, long count)
+{
+	Qio *io;
+	Ctlio *cio;
+	Isoio *iso;
+	ulong delta;
+
+	pollcheck(ep->hp);
+
+	ddeprint("ehci: epwrite ep%d.%d\n", ep->dev->nb, ep->nb);
+	if(ep->aux == nil)
+		panic("ehci: epwrite: not open");
+	switch(ep->ttype){
+	case Tctl:
+		cio = ep->aux;
+		return epctlio(ep, cio, a, count);
+	case Tbulk:
+		io = ep->aux;
+		if(ep->clrhalt)
+			clrhalt(ep);
+		return epio(ep, &io[OWRITE], a, count, 1);
+	case Tintr:
+		io = ep->aux;
+		delta = TK2MS(MACHP(0)->ticks) - io[OWRITE].iotime + 1;
+		if(delta < ep->pollival)
+			tsleep(&up->sleep, return0, 0, ep->pollival - delta);
+		if(ep->clrhalt)
+			clrhalt(ep);
+		return epio(ep, &io[OWRITE], a, count, 1);
+	case Tiso:
+		iso = ep->aux;
+		return episowrite(ep, iso, a, count);
+	}
+	return -1;
+}
+
+static void
+isofsinit(Ep *ep, Isoio *iso)
+{
+	long left;
+	Sitd *td, *ltd;
+	int i;
+	ulong frno;
+
+	left = 0;
+	ltd = nil;
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		td = iso->sitdps[frno] = sitdalloc();
+		td->data = iso->data + i * ep->maxpkt;
+		td->epc = ep->dev->port << Stdportshift;
+		td->epc |= ep->dev->hub << Stdhubshift;
+		td->epc |= ep->nb << Stdepshift;
+		td->epc |= ep->dev->nb << Stddevshift;
+		td->mfs = (034 << Stdscmshift) | (1 << Stdssmshift);
+		if(ep->mode == OREAD){
+			td->epc |= Stdin;
+			td->mdata = ep->maxpkt;
+		}else{
+			td->mdata = (ep->hz+left) * ep->pollival / 1000;
+			td->mdata *= ep->samplesz;
+			left = (ep->hz+left) * ep->pollival % 1000;
+			if(td->mdata > ep->maxpkt){
+				print("ehci: ep%d.%d: size > maxpkt\n",
+					ep->dev->nb, ep->nb);
+				print("size = %ld max = %ld\n",
+					td->mdata,ep->maxpkt);
+				td->mdata = ep->maxpkt;
+			}
+		}
+		coherence();
+
+		sitdinit(iso, td);
+		if(ltd != nil)
+			ltd->next = td;
+		ltd = td;
+		frno = TRUNC(frno+ep->pollival, Nisoframes);
+	}
+	ltd->next = iso->sitdps[iso->td0frno];
+	coherence();
+}
+
+static void
+isohsinit(Ep *ep, Isoio *iso)
+{
+	Itd *td, *ltd;
+	int ival, p;
+	long left;
+	ulong frno, i, pa;
+
+	iso->hs = 1;
+	ival = 1;
+	if(ep->pollival > 8)
+		ival = ep->pollival/8;
+	left = 0;
+	ltd = nil;
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		td = iso->itdps[frno] = itdalloc();
+		td->data = iso->data + i * 8  * iso->maxsize;
+		pa = PADDR(td->data) & ~0xFFF;
+		for(p = 0; p < 8; p++)
+			td->buffer[i] = pa + p * 0x1000;
+		td->buffer[0] = PADDR(iso->data) & ~0xFFF;
+		td->buffer[0] |= ep->nb << Itdepshift;
+		td->buffer[0] |= ep->dev->nb << Itddevshift;
+		if(ep->mode == OREAD)
+			td->buffer[1] |= Itdin;
+		else
+			td->buffer[1] |= Itdout;
+		td->buffer[1] |= ep->maxpkt << Itdmaxpktshift;
+		td->buffer[2] |= ep->ntds << Itdntdsshift;
+
+		if(ep->mode == OREAD)
+			td->mdata = 8 * iso->maxsize;
+		else{
+			td->mdata = (ep->hz + left) * ep->pollival / 1000;
+			td->mdata *= ep->samplesz;
+			left = (ep->hz + left) * ep->pollival % 1000;
+		}
+		coherence();
+		itdinit(iso, td);
+		if(ltd != nil)
+			ltd->next = td;
+		ltd = td;
+		frno = TRUNC(frno + ival, Nisoframes);
+	}
+}
+
+static void
+isoopen(Ctlr *ctlr, Ep *ep)
+{
+	int ival;		/* pollival in ms */
+	int tpf;		/* tds per frame */
+	int i, n, w, woff;
+	ulong frno;
+	Isoio *iso;
+
+	iso = ep->aux;
+	switch(ep->mode){
+	case OREAD:
+		iso->tok = Tdtokin;
+		break;
+	case OWRITE:
+		iso->tok = Tdtokout;
+		break;
+	default:
+		error("iso i/o is half-duplex");
+	}
+	iso->usbid = (ep->nb<<7)|(ep->dev->nb & Devmax);
+	iso->state = Qidle;
+	iso->debug = ep->debug;
+	ival = ep->pollival;
+	tpf = 1;
+	if(ep->dev->speed == Highspeed){
+		tpf = 8;
+		if(ival <= 8)
+			ival = 1;
+		else
+			ival /= 8;
+	}
+	iso->nframes = Nisoframes / ival;
+	if(iso->nframes < 3)
+		error("uhci isoopen bug");	/* we need at least 3 tds */
+	iso->maxsize = ep->ntds * ep->maxpkt;
+	if(ctlr->load + ep->load > 800)
+		print("usb: ehci: bandwidth may be exceeded\n");
+	ilock(ctlr);
+	ctlr->load += ep->load;
+	ctlr->isoload += ep->load;
+	ctlr->nreqs++;
+	dprint("ehci: load %uld isoload %uld\n", ctlr->load, ctlr->isoload);
+	diprint("iso nframes %d pollival %uld ival %d maxpkt %uld ntds %d\n",
+		iso->nframes, ep->pollival, ival, ep->maxpkt, ep->ntds);
+	iunlock(ctlr);
+	if(ctlr->poll.does)
+		wakeup(&ctlr->poll);
+
+	/*
+	 * From here on this cannot raise errors
+	 * unless we catch them and release here all memory allocated.
+	 */
+	assert(ep->maxpkt > 0 && ep->ntds > 0 && ep->ntds < 4);
+	assert(ep->maxpkt <= 1024);
+	iso->tdps = smalloc(sizeof(uintptr) * Nisoframes);
+	iso->data = smalloc(iso->nframes * tpf * ep->ntds * ep->maxpkt);
+	iso->td0frno = TRUNC(ctlr->opio->frno + 10, Nisoframes);
+	/* read: now; write: 1s ahead */
+
+	if(ep->dev->speed == Highspeed)
+		isohsinit(ep, iso);
+	else
+		isofsinit(ep, iso);
+	iso->tdu = iso->tdi = iso->itdps[iso->td0frno];
+	iso->stdu = iso->stdi = iso->sitdps[iso->td0frno];
+
+	ilock(ctlr);
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		*iso->tdps[frno] = ctlr->frames[frno];
+		frno = TRUNC(frno+ival, Nisoframes);
+	}
+
+	/*
+	 * Iso uses a virtual frame window of Nisoframes, and we must
+	 * fill the actual ctlr frame array by placing ctlr->nframes/Nisoframes
+	 * copies of the window in the frame array.
+	 */
+	assert(ctlr->nframes >= Nisoframes && Nisoframes >= iso->nframes);
+	assert(Nisoframes >= Nintrleafs);
+	n = ctlr->nframes / Nisoframes;
+	for(w = 0; w < n; w++){
+		frno = iso->td0frno;
+		woff = w * Nisoframes;
+		for(i = 0; i < iso->nframes ; i++){
+			assert(woff+frno < ctlr->nframes);
+			assert(iso->tdps[frno] != nil);
+			if(ep->dev->speed == Highspeed)
+				ctlr->frames[woff+frno] = PADDR(iso->tdps[frno])
+					|Litd;
+			else
+				ctlr->frames[woff+frno] = PADDR(iso->tdps[frno])
+					|Lsitd;
+			frno = TRUNC(frno+ep->pollival, Nisoframes);
+		}
+	}
+	iso->next = ctlr->iso;
+	ctlr->iso = iso;
+	iso->state = Qdone;
+	iunlock(ctlr);
+	if(debug > 1 || iso->debug >1)
+		isodump(iso, 0);
+}
+
+/*
+ * Allocate the endpoint and set it up for I/O
+ * in the controller. This must follow what's said
+ * in Ep regarding configuration, including perhaps
+ * the saved toggles (saved on a previous close of
+ * the endpoint data file by epclose).
+ */
+static void
+epopen(Ep *ep)
+{
+	Ctlr *ctlr;
+	Ctlio *cio;
+	Qio *io;
+	int usbid;
+
+	ctlr = ep->hp->aux;
+	deprint("ehci: epopen ep%d.%d\n", ep->dev->nb, ep->nb);
+	if(ep->aux != nil)
+		panic("ehci: epopen called with open ep");
+	if(waserror()){
+		free(ep->aux);
+		ep->aux = nil;
+		nexterror();
+	}
+	switch(ep->ttype){
+	case Tnone:
+		error("endpoint not configured");
+	case Tiso:
+		ep->aux = smalloc(sizeof(Isoio));
+		isoopen(ctlr, ep);
+		break;
+	case Tctl:
+		cio = ep->aux = smalloc(sizeof(Ctlio));
+		cio->debug = ep->debug;
+		cio->ndata = -1;
+		cio->data = nil;
+		if(ep->dev->isroot != 0 && ep->nb == 0)	/* root hub */
+			break;
+		cio->qh = qhalloc(ctlr, ep, cio, "epc");
+		break;
+	case Tbulk:
+		ep->pollival = 1;	/* assume this; doesn't really matter */
+		/* and fall... */
+	case Tintr:
+		io = ep->aux = smalloc(sizeof(Qio)*2);
+		io[OREAD].debug = io[OWRITE].debug = ep->debug;
+		usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb &Devmax);
+		if(ep->mode != OREAD){
+			if(ep->toggle[OWRITE] != 0)
+				io[OWRITE].toggle = Tddata1;
+			else
+				io[OWRITE].toggle = Tddata0;
+			io[OWRITE].tok = Tdtokout;
+			io[OWRITE].usbid = usbid;
+			io[OWRITE].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */
+			io[OWRITE].qh = qhalloc(ctlr, ep, io+OWRITE, "epw");
+		}
+		if(ep->mode != OWRITE){
+			if(ep->toggle[OREAD] != 0)
+				io[OREAD].toggle = Tddata1;
+			else
+				io[OREAD].toggle = Tddata0;
+			io[OREAD].tok = Tdtokin;
+			io[OREAD].usbid = usbid;
+			io[OREAD].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */
+			io[OREAD].qh = qhalloc(ctlr, ep, io+OREAD, "epr");
+		}
+		break;
+	}
+	if(debug>1 || ep->debug)
+		dump(ep->hp);
+	deprint("ehci: epopen done\n");
+	poperror();
+}
+
+static void
+cancelio(Ctlr *ctlr, Qio *io)
+{
+	Qh *qh;
+
+	ilock(ctlr);
+	qh = io->qh;
+	if(io == nil || io->qh == nil || io->qh->state == Qclose){
+		iunlock(ctlr);
+		return;
+	}
+	dqprint("ehci: cancelio for qh %#p state %s\n",
+		qh, qhsname[qh->state]);
+	aborttds(qh);
+	qh->state = Qclose;
+	iunlock(ctlr);
+	if(!waserror()){
+		tsleep(&up->sleep, return0, 0, Abortdelay);
+		poperror();
+	}
+	wakeup(io);
+	qlock(io);
+	/* wait for epio if running */
+	qunlock(io);
+
+	qhfree(ctlr, qh);
+	io->qh = nil;
+}
+
+static void
+cancelisoio(Ctlr *ctlr, Isoio *iso, int pollival, ulong load)
+{
+	int frno, i, n, t, w, woff;
+	ulong *lp, *tp;
+	Isoio **il;
+	Itd *td;
+	Sitd *std;
+
+	ilock(ctlr);
+	if(iso->state == Qclose){
+		iunlock(ctlr);
+		return;
+	}
+	ctlr->nreqs--;
+	if(iso->state != Qrun && iso->state != Qdone)
+		panic("bad iso state");
+	iso->state = Qclose;
+	if(ctlr->isoload < load)
+		panic("ehci: low isoload");
+	ctlr->isoload -= load;
+	ctlr->load -= load;
+	for(il = &ctlr->iso; *il != nil; il = &(*il)->next)
+		if(*il == iso)
+			break;
+	if(*il == nil)
+		panic("cancleiso: not found");
+	*il = iso->next;
+
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		tp = iso->tdps[frno];
+		if(iso->hs != 0){
+			td = iso->itdps[frno];
+			for(t = 0; t < nelem(td->csw); t++)
+				td->csw[1] &= ~(Itdioc|Itdactive);
+		}else{
+			std = iso->sitdps[frno];
+			std->csw &= ~(Stdioc|Stdactive);
+		}
+		coherence();
+		for(lp=&ctlr->frames[frno]; !(*lp & Lterm); lp = &LPTR(*lp)[0])
+			if(LPTR(*lp) == tp)
+				break;
+		if(*lp & Lterm)
+			panic("cancelisoio: td not found");
+		*lp = tp[0];
+		/*
+		 * Iso uses a virtual frame window of Nisoframes, and we must
+		 * restore pointers in copies of the window kept at ctlr->frames.
+		 */
+		if(lp == &ctlr->frames[frno]){
+			n = ctlr->nframes / Nisoframes;
+			for(w = 1; w < n; w++){
+				woff = w * Nisoframes;
+				ctlr->frames[woff+frno] = *lp;
+			}
+		}
+		frno = TRUNC(frno+pollival, Nisoframes);
+	}
+	iunlock(ctlr);
+
+	/*
+	 * wakeup anyone waiting for I/O and
+	 * wait to be sure no I/O is in progress in the controller.
+	 * and then wait to be sure episo* is no longer running.
+	 */
+	wakeup(iso);
+	diprint("cancelisoio iso %#p waiting for I/O to cease\n", iso);
+	tsleep(&up->sleep, return0, 0, 5);
+	qlock(iso);
+	qunlock(iso);
+	diprint("cancelisoio iso %#p releasing iso\n", iso);
+
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		if(iso->hs != 0)
+			itdfree(iso->itdps[frno]);
+		else
+			sitdfree(iso->sitdps[frno]);
+		iso->tdps[frno] = nil;
+		frno = TRUNC(frno+pollival, Nisoframes);
+	}
+	free(iso->tdps);
+	iso->tdps = nil;
+	free(iso->data);
+	iso->data = nil;
+}
+
+static void
+epclose(Ep *ep)
+{
+	Qio *io;
+	Ctlio *cio;
+	Isoio *iso;
+	Ctlr *ctlr;
+
+	ctlr = ep->hp->aux;
+	deprint("ehci: epclose ep%d.%d\n", ep->dev->nb, ep->nb);
+
+	if(ep->aux == nil)
+		panic("ehci: epclose called with closed ep");
+	switch(ep->ttype){
+	case Tctl:
+		cio = ep->aux;
+		cancelio(ctlr, cio);
+		free(cio->data);
+		cio->data = nil;
+		break;
+	case Tintr:
+	case Tbulk:
+		io = ep->aux;
+		ep->toggle[OREAD] = ep->toggle[OWRITE] = 0;
+		if(ep->mode != OWRITE){
+			cancelio(ctlr, &io[OREAD]);
+			if(io[OREAD].toggle == Tddata1)
+				ep->toggle[OREAD] = 1;
+		}
+		if(ep->mode != OREAD){
+			cancelio(ctlr, &io[OWRITE]);
+			if(io[OWRITE].toggle == Tddata1)
+				ep->toggle[OWRITE] = 1;
+		}
+		break;
+	case Tiso:
+		iso = ep->aux;
+		cancelisoio(ctlr, iso, ep->pollival, ep->load);
+		break;
+	default:
+		panic("epclose: bad ttype");
+	}
+	free(ep->aux);
+	ep->aux = nil;
+}
+
+/*
+ * return smallest power of 2 >= n
+ */
+static int
+flog2(int n)
+{
+	int i;
+
+	for(i = 0; (1 << i) < n; i++)
+		;
+	return i;
+}
+
+/*
+ * build the periodic scheduling tree:
+ * framesize must be a multiple of the tree size
+ */
+static void
+mkqhtree(Ctlr *ctlr)
+{
+	int i, n, d, o, leaf0, depth;
+	Qh **tree;
+	Qtree *qt;
+	Qh *qh;
+	ulong leafs[Nintrleafs];
+
+	depth = flog2(Nintrleafs);
+	n = (1 << (depth+1)) - 1;
+	qt = mallocz(sizeof(*qt), 1);
+	if(qt == nil)
+		panic("ehci: mkqhtree: no memory");
+	qt->nel = n;
+	qt->depth = depth;
+	qt->bw = mallocz(n * sizeof(qt->bw), 1);
+	qt->root = tree = mallocz(n * sizeof(Qh *), 1);
+	if(qt->bw == nil || tree == nil)
+		panic("ehci: mkqhtree: no memory");
+	for(i = 0; i < n; i++){
+		qh = tree[i] = edalloc();
+		if(qh == nil)
+			panic("ehci: mkqhtree: no memory");
+		qh->nlink = qh->alink = qh->link = Lterm;
+		qh->csw = Tdhalt;
+		qh->state = Qidle;
+		if(i > 0)
+			qhlinkqh(tree[i], tree[(i-1)/2]);
+	}
+	ctlr->ntree = i;
+	dprint("ehci: tree: %d endpoints allocated\n", i);
+
+	/* distribute leaves evenly round the frame list */
+	leaf0 = n / 2;
+	for(i = 0; i < Nintrleafs; i++){
+		o = 0;
+		for(d = 0; d < depth; d++){
+			o <<= 1;
+			if(i & (1 << d))
+				o |= 1;
+		}
+		if(leaf0 + o >= n){
+			print("leaf0=%d o=%d i=%d n=%d\n", leaf0, o, i, n);
+			break;
+		}
+		leafs[i] = PADDR(tree[leaf0 + o]) | Lqh;
+	}
+	assert((ctlr->nframes % Nintrleafs) == 0);
+	for(i = 0; i < ctlr->nframes; i += Nintrleafs)
+		memmove(ctlr->frames + i, leafs, sizeof(leafs));
+	ctlr->tree = qt;
+}
+
+static void
+ehcimeminit(Ctlr *ctlr)
+{
+	int frsize;
+	Eopio *opio;
+	int i;
+
+	opio = ctlr->opio;
+	frsize = ctlr->nframes*sizeof(ulong);
+	assert((frsize & 0xFFF) == 0);		/* must be 4k aligned */
+	ctlr->frames = xspanalloc(frsize, frsize, 0);
+	if(ctlr->frames == nil)
+		panic("ehci reset: no memory");
+
+	for (i = 0; i < ctlr->nframes; i++)
+		ctlr->frames[i] = Lterm;
+	opio->frbase = PADDR(ctlr->frames);
+	opio->frno = 0;
+	coherence();
+
+	qhalloc(ctlr, nil, nil, nil);	/* init async list */
+	mkqhtree(ctlr);			/* init sync list */
+	edfree(edalloc());		/* try to get some ones pre-allocated */
+
+	dprint("ehci %#p flb %#ulx frno %#ulx\n",
+		ctlr->capio, opio->frbase, opio->frno);
+}
+
+static void
+init(Hci *hp)
+{
+	Ctlr *ctlr;
+	Eopio *opio;
+	int i;
+
+	hp->highspeed = 1;
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	dprint("ehci %#p init\n", ctlr->capio);
+
+	ilock(ctlr);
+	/*
+	 * Unless we activate frroll interrupt
+	 * some machines won't post other interrupts.
+	 */
+	opio->intr = Iusb|Ierr|Iportchg|Ihcerr|Iasync;
+	opio->cmd |= Cpse;
+	coherence();
+	opio->cmd |= Case;
+	coherence();
+	ehcirun(ctlr, 1);
+	opio->config = Callmine;	/* reclaim all ports */
+	coherence();
+
+	for (i = 0; i < hp->nports; i++)
+		opio->portsc[i] = Pspower;
+	coherence();
+	iunlock(ctlr);
+
+	if(debug > 1)
+		dump(hp);
+}
+
+static void
+ehcireset(Ctlr *ctlr)
+{
+	Eopio *opio;
+	int i;
+
+	ilock(ctlr);
+	dprint("ehci %#p reset\n", ctlr->capio);
+	opio = ctlr->opio;
+
+	/*
+	 * Turn off legacy mode. Some controllers won't
+	 * interrupt us as expected otherwise.
+	 */
+	ehcirun(ctlr, 0);
+
+	/* clear high 32 bits of address signals if it's 64 bits capable.
+	 * This is probably not needed but it does not hurt and others do it.
+	 */
+	if((ctlr->capio->capparms & C64) != 0){
+		dprint("ehci: 64 bits\n");
+		opio->seg = 0;
+	}
+
+	if(ehcidebugcapio != ctlr->capio){
+		opio->cmd |= Chcreset;	/* controller reset */
+		coherence();
+		for(i = 0; i < 100; i++){
+			if((opio->cmd & Chcreset) == 0)
+				break;
+			delay(1);
+		}
+		if(i == 100)
+			print("ehci %#p controller reset timed out\n", ctlr->capio);
+	}
+
+	/* requesting more interrupts per µframe may miss interrupts */
+	opio->cmd |= Citc8;		/* 1 intr. per ms */
+	coherence();
+	switch(opio->cmd & Cflsmask){
+	case Cfls1024:
+		ctlr->nframes = 1024;
+		break;
+	case Cfls512:
+		ctlr->nframes = 512;
+		break;
+	case Cfls256:
+		ctlr->nframes = 256;
+		break;
+	default:
+		panic("ehci: unknown fls %ld", opio->cmd & Cflsmask);
+	}
+	dprint("ehci: %d frames\n", ctlr->nframes);
+	iunlock(ctlr);
+}
+
+static void
+setdebug(Hci*, int d)
+{
+	debug = d;
+}
+
+static void
+shutdown(Hci *hp)
+{
+	int i;
+	Ctlr *ctlr;
+	Eopio *opio;
+
+	ctlr = hp->aux;
+	ilock(ctlr);
+	opio = ctlr->opio;
+	opio->cmd |= Chcreset;		/* controller reset */
+	coherence();
+	for(i = 0; i < 100; i++){
+		if((opio->cmd & Chcreset) == 0)
+			break;
+		delay(1);
+	}
+	if(i >= 100)
+		print("ehci %#p controller reset timed out\n", ctlr->capio);
+	delay(100);
+	ehcirun(ctlr, 0);
+	opio->frbase = 0;
+	coherence();
+	iunlock(ctlr);
+}
+
+/*
+ * omap3530-specific setup
+ */
+
+enum {
+	/* opio->insn[5] bits */
+	Control		= 1<<31,  /* set to start access, cleared when done */
+	Write		= 2<<22,
+	Read		= 3<<22,
+	Portsh		= 24,
+	Regaddrsh	= 16,		/* 0x2f means use extended reg addr */
+	Eregaddrsh	= 8,
+
+	/* phy reg addresses */
+	Funcctlreg	= 4,
+	Ifcctlreg	= 7,
+
+	Phystppullupoff	= 0x90,		/* on is 0x10 */
+
+	Phyrstport2	= 147,		/* gpio # */
+
+};
+
+static void
+wrulpi(Eopio *opio, int port, int reg, uchar data)
+{
+	opio->insn[5] = Control | port << Portsh | Write | reg << Regaddrsh |
+		data;
+	coherence();
+	/*
+	 * this seems contrary to the skimpy documentation in the manual
+	 * but inverting the test hangs forever.
+	 */
+	while (!(opio->insn[5] & Control))
+		;
+}
+
+static int
+reset(Hci *hp)
+{
+	Ctlr *ctlr;
+	Ecapio *capio;
+	Eopio *opio;
+	Uhh *uhh;
+	static int beenhere;
+
+	if (beenhere)
+		return -1;
+	beenhere = 1;
+
+	if(getconf("*nousbehci") != nil || probeaddr(PHYSEHCI) < 0)
+		return -1;
+
+	ctlr = mallocz(sizeof(Ctlr), 1);
+	/*
+	 * don't bother with vmap; i/o space is all mapped anyway,
+	 * and a size less than 1MB will blow an assertion in mmukmap.
+	 */
+	ctlr->capio = capio = (Ecapio *)PHYSEHCI;
+//	ctlr->capio = capio = vmap(PHYSEHCI, 1024);
+	ctlr->opio = opio = (Eopio*)((uintptr)capio + (capio->cap & 0xff));
+
+	hp->aux = ctlr;
+	hp->port = (uintptr)ctlr->capio;
+	hp->irq = 77;
+	hp->nports = capio->parms & Cnports;
+
+	ddprint("echi: %s, ncc %lud npcc %lud\n",
+		capio->parms & 0x10000 ? "leds" : "no leds",
+		(capio->parms >> 12) & 0xf, (capio->parms >> 8) & 0xf);
+	ddprint("ehci: routing %s, %sport power ctl, %d ports\n",
+		capio->parms & 0x40 ? "explicit" : "automatic",
+		capio->parms & 0x10 ? "" : "no ", hp->nports);
+
+	ehcireset(ctlr);
+	ehcimeminit(ctlr);
+
+	/* omap35-specific set up */
+	/* bit 5 `must be set to 1 for proper behavior', spruf98d §23.2.6.7.17 */
+	opio->insn[4] |= 1<<5;
+	coherence();
+
+	/* insn[5] is for both utmi and ulpi, depending on hostconfig mode */
+	uhh = (Uhh *)PHYSUHH;
+	if (uhh->hostconfig & P1ulpi_bypass) {		/* utmi port 1 active */
+		/* not doing this */
+		iprint("usbehci: bypassing ulpi on port 1!\n");
+		opio->insn[5] &= ~(MASK(4) << 13);
+		opio->insn[5] |= 1 << 13;		/* select port 1 */
+		coherence();
+	} else {					/* ulpi port 1 active */
+		/* TODO may need to reset gpio port2 here */
+
+		/* disable integrated stp pull-up resistor */
+		wrulpi(opio, 1, Ifcctlreg, Phystppullupoff);
+
+		/* force phy to `high-speed' */
+		wrulpi(opio, 1, Funcctlreg, 0x40);
+	}
+
+	intrenable(78, interrupt, hp, UNKNOWN, "usbtll");
+	intrenable(92, interrupt, hp, UNKNOWN, "usb otg");
+	intrenable(93, interrupt, hp, UNKNOWN, "usb otg dma");
+
+	/*
+	 * Linkage to the generic HCI driver.
+	 */
+	hp->init = init;
+	hp->dump = dump;
+	hp->interrupt = interrupt;
+	hp->epopen = epopen;
+	hp->epclose = epclose;
+	hp->epread = epread;
+	hp->epwrite = epwrite;
+	hp->seprintep = seprintep;
+	hp->portenable = portenable;
+	hp->portreset = portreset;
+	hp->portstatus = portstatus;
+	hp->shutdown = shutdown;
+	hp->debug = setdebug;
+	hp->type = "ehci";
+	return 0;
+}
+
+void
+usbehcilink(void)
+{
+	addhcitype("ehci", reset);
+}

+ 168 - 0
sys/src/9/omap/words

@@ -0,0 +1,168 @@
+beagleboard rev c3:
+cortex-a8 cpu: arm v7-a arch. rev 3, 500MHz, dual-issue
+OMAP3530-GP rev 2, CPU-OPP2 L3-165MHz
+OMAP3 Beagle board + LPDDR/NAND
+DRAM:  256 MB
+NAND:  256 MiB
+Board revision C
+Serial #784200230000000004013f790401d018
+
+igepv2 board:
+cortex-a8 cpu: arm v7-a arch. rev 3, 720MHz, dual-issue
+OMAP3530-GP ES3.1, CPU-OPP2 L3-165MHz
+IGEP v2.x rev. B + LPDDR/ONENAND
+
+omap3530 SoC
+CORE_CLK runs at 26MHz
+see spruf98d from ti.com (/public/doc/ti/omap35x.ref.spruf98d.pdf)
+
+separate i & d tlbs, each 32 entries
+	can invalidate i, d or both tlbs by { all, mva, or asid match }
+
+i & d L1 caches, 16K each, 4 ways, 64 sets, 64-byte lines
+	i is VIPT, d is PIPT
+	no `test and clean D & U all' operations
+	no prefetching, no cache maintenance
+	can invalidate i, d or both cache but not D & U all
+	can invalidate entire i-cache only
+	can clean or invalidate by set and way data/unified cache
+unified L2 PIPT cache, 256K, 8 ways, 512 sets, 64-byte lines
+
+l3 interconnect firewalls are all off at boot time, except for a bit of
+	secure ram
+sram at 0x40200000 size 1MB
+l4 interconnect firewalls seem to be sane at boot time
+
+___
+The state of the Beagleboard/IGEPv2 (TI OMAP35 SoC, Cortex-A8) port.
+
+Plan 9 runs on the IGEPv2 board.
+
+On the Beagleboard, Plan 9 is not yet usable but it gets as far as
+trying to access the USB ethernet (since the Beagleboard has no
+built-in ethernet and must use USB ethernet).
+
+IGEP Ethernet
+
+The igep's smsc9221 ethernet consumes a lot of system time.  The
+design decision to use fifos rather than buffer rings and to not
+incorporate dma into the ethernet controller is probably responsible.
+With only a single core, running the 9221 consumes a lot of the
+available CPU time.  It's probably worth trying to use the system dma
+controller again.
+
+USB
+
+The ohci and ehci controllers are seen, but no devices yet.
+
+There are four USB errata that need to be looked into for the igepv2
+(silicon 3.1) at least.  From the omap3530 errata (rev e):
+
+- 3.1.1.130 only one usb dma channel (rx or tx) can be active
+	at one time: use interrupt mode instead
+- 3.1.1.144 otg soft reset doesn't work right
+- 3.1.1.183 ohci and ehci controllers cannot work concurrently
+- §3.1.3 usb limitations: all ports must be configured to identical speeds
+	(high vs full/low)
+
+Flash
+
+access to nand or spi flash would be handy for nvram and small
+fossils.  flash access isn't well documented.  inferno implements
+these software layers: ecc, translation (for wear-levelling and bad
+sectors), common flash code and specific drivers for flash chips.
+
+VFPv3 Floating Point
+
+The Cortex-A8 has VFPv3 floating point, which uses different opcodes
+than 5c/5l currently generate.  New 5c or 5l is in the works.
+
+Video
+
+The video controller may be documented and source is available for a
+Linux driver.
+
+There are a few rough edges:
+
+- the clock.c scheduling rate (HZ) is quite approximate.  The OMAP
+timers are complex, but one could eventually do better (or just let
+timesync compensate).
+
+- Serial console printing is rudimentary.  I tried repeatedly to hook
+into devuart, but failed.  "g 'CRUDEPRINT|LASTRESORT' ." should find
+the workarounds.
+
+- User processes are limited to 512MB (mainly by the IGEPv2 Ethernet
+being at 0x2c000000), which isn't a problem since Beagleboards only
+have 256MB of dram and IGEPv2s have 512MB.
+
+- might use ucalloc.c to allocate uncached scratch space for generated code
+in coproc.c.
+
+- the C implementation of cache primitives failed with mmu off; still true?
+
+- unlock, setup: protect module register target APE (PM_RT) per spruf98c §1.6.7
+
+- setup mpp (multi-purpose pins)?
+
+___
+	memory map (mostly from omap35x ref)
+hex addr	size	what
+----
+2c000000	?	smc 9221 ethernet
+
+40000000	112K	boot rom, top of user space
+40200000	64K	sram
+
+48000000	16MB	L4 core
+48002000	8K	system control (scm)
+48004000	16K	clock manager
+48040000	8K	L4-core config
+48050000	4K	graphics
+48062000	4K	usb tll
+48064000	1K	usb uhh_config
+48064400	1K	ohci
+48064800	1K	ehci
+4806a000	8K	8250 uart0
+4806c000	8K	8250 uart1
+48086000	4K	gptimer10
+48088000	4K	gptimer11
+4809c000	8K	mmc/sd goo
+480ab000	8K	hs usb otg
+480ad000	8K	mmc/sd goo
+480b4000	8K	mmc/sd goo
+480c7000		device intr controller
+48200000	2K	intr ctlr (intc)
+
+48300000	256K	L4-wakeup
+48304000	4K	gptimer12
+48318000	8K	gptimer1
+
+49000000	1MB	L4 peripherals
+49020000	8K	8250 uart2 (with exposed connector for console)
+49032000	4K	gptimer2
+49034000	4K	gptimer3
+⋯
+49040000	4K	gptimer9
+49050000	8K	gpio2
+⋯
+49058000	8K	gpio6
+
+50000000	64K	graphics accelerator
+
+68000000	1K	L3 config (rt)
+68004000	1K	L3 hs usb host
+68004400	1K	L3 hs usb otg
+68005400	1K	L3 graphics
+68006800	1K	L4-core config
+68010000		L3 protection mechanism
+
+6e000000	?	gpmc
+
+80000000	256MB	dram on beagle
+		512MB	dram on igep
+
+c0000000	1GB	kernel virtual space, mapped to 80000000
+
+apparently the vector address (0 or 0xffff0000) is virtual,
+so we're expected to map it to ram.