diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/kw |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/kw')
47 files changed, 14623 insertions, 0 deletions
diff --git a/sys/src/9/kw/arch.c b/sys/src/9/kw/arch.c new file mode 100755 index 000000000..c800ffc77 --- /dev/null +++ b/sys/src/9/kw/arch.c @@ -0,0 +1,222 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" + +#include <tos.h> +#include "ureg.h" + +#include "arm.h" + +/* + * A lot of this stuff doesn't belong here + * but this is a convenient dumping ground for + * later sorting into the appropriate buckets. + */ + +/* Give enough context in the ureg to produce a kernel stack for + * a sleeping process + */ +void +setkernur(Ureg* ureg, Proc* p) +{ + ureg->pc = p->sched.pc; + ureg->sp = p->sched.sp+4; + ureg->r14 = PTR2UINT(sched); +} + +/* + * called in sysfile.c + */ +void +evenaddr(uintptr addr) +{ + if(addr & 3){ + postnote(up, 1, "sys: odd address", NDebug); + error(Ebadarg); + } +} + +/* go to user space */ +void +kexit(Ureg*) +{ + uvlong t; + Tos *tos; + + /* precise time accounting, kernel exit */ + tos = (Tos*)(USTKTOP-sizeof(Tos)); + t = fastticks(nil); + tos->kcycles += t - up->kentry; + tos->pcycles = up->pcycles; + tos->cyclefreq = Frequency; + tos->pid = up->pid; + + /* make visible immediately to user proc */ + cachedwbinvse(tos, sizeof *tos); +} + +/* + * return the userpc the last exception happened at + */ +uintptr +userpc(void) +{ + Ureg *ureg = up->dbgreg; + return ureg->pc; +} + +/* This routine must save the values of registers the user is not permitted + * to write from devproc and then restore the saved values before returning. + */ +void +setregisters(Ureg* ureg, char* pureg, char* uva, int n) +{ + USED(ureg, pureg, uva, n); +} + +/* + * this is the body for all kproc's + */ +static void +linkproc(void) +{ + spllo(); + up->kpfun(up->kparg); + pexit("kproc exiting", 0); +} + +/* + * setup stack and initial PC for a new kernel proc. This is architecture + * dependent because of the starting stack location + */ +void +kprocchild(Proc *p, void (*func)(void*), void *arg) +{ + p->sched.pc = PTR2UINT(linkproc); + p->sched.sp = PTR2UINT(p->kstack+KSTACK); + + p->kpfun = func; + p->kparg = arg; +} + +/* + * pc output by dumpaproc + */ +uintptr +dbgpc(Proc* p) +{ + Ureg *ureg; + + ureg = p->dbgreg; + if(ureg == 0) + return 0; + + return ureg->pc; +} + +/* + * set mach dependent process state for a new process + */ +void +procsetup(Proc* p) +{ + fpusysprocsetup(p); +} + +/* + * Save the mach dependent part of the process state. + */ +void +procsave(Proc* p) +{ + uvlong t; + + cycles(&t); + p->pcycles += t; + + fpuprocsave(p); +} + +void +procrestore(Proc* p) +{ + uvlong t; + + if(p->kp) + return; + t = lcycles(); + p->pcycles -= t; + + fpuprocrestore(p); +} + +int +userureg(Ureg* ureg) +{ + return (ureg->psr & PsrMask) == PsrMusr; +} + +/* + * atomic ops + * make sure that we don't drag in the C library versions + */ + +long +_xdec(long *p) +{ + int s, v; + + s = splhi(); + v = --*p; + splx(s); + return v; +} + +void +_xinc(long *p) +{ + int s; + + s = splhi(); + ++*p; + splx(s); +} + +int +ainc(int *p) +{ + int s, v; + + s = splhi(); + v = ++*p; + splx(s); + return v; +} + +int +adec(int *p) +{ + int s, v; + + s = splhi(); + v = --*p; + splx(s); + return v; +} + +int +cas32(void* addr, u32int old, u32int new) +{ + int r, s; + + s = splhi(); + if(r = (*(u32int*)addr == old)) + *(u32int*)addr = new; + splx(s); + if (r) + coherence(); + return r; +} diff --git a/sys/src/9/kw/archkw.c b/sys/src/9/kw/archkw.c new file mode 100755 index 000000000..d6fd700fa --- /dev/null +++ b/sys/src/9/kw/archkw.c @@ -0,0 +1,519 @@ +/* + * stuff specific to marvell's kirkwood architecture + * as seen in the sheevaplug + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" +#include "io.h" + +#include "../port/netif.h" +#include "etherif.h" +#include "../port/flashif.h" + +#include "arm.h" + +enum { + L2writeback = 1, + Debug = 0, +}; + +typedef struct GpioReg GpioReg; +struct GpioReg { + ulong dataout; + ulong dataoutena; + ulong blinkena; + ulong datainpol; + ulong datain; + ulong intrcause; + ulong intrmask; + ulong intrlevelmask; +}; + +typedef struct L2uncache L2uncache; +typedef struct L2win L2win; +struct L2uncache { + struct L2win { + ulong base; /* phys addr */ + ulong size; + } win[4]; +}; + +enum { + /* L2win->base bits */ + L2enable = 1<<0, +}; + +typedef struct Dramctl Dramctl; +struct Dramctl { + ulong ctl; + ulong ddrctllo; + struct { + ulong lo; + ulong hi; + } time; + ulong addrctl; + ulong opagectl; + ulong oper; + ulong mode; + ulong extmode; + ulong ddrctlhi; + ulong ddr2timelo; + ulong operctl; + struct { + ulong lo; + ulong hi; + } mbusctl; + ulong mbustimeout; + ulong ddrtimehi; + ulong sdinitctl; + ulong extsdmode1; + ulong extsdmode2; + struct { + ulong lo; + ulong hi; + } odtctl; + ulong ddrodtctl; + ulong rbuffsel; + + ulong accalib; + ulong dqcalib; + ulong dqscalib; +}; + +/* unused so far */ +typedef struct SDramdReg SDramdReg; +struct SDramdReg { + struct { + ulong base; + ulong size; + } win[4]; +}; + +typedef struct Addrmap Addrmap; +typedef struct Addrwin Addrwin; +struct Addrmap { + struct Addrwin { + ulong ctl; /* see Winenable in io.h */ + ulong base; + ulong remaplo; + ulong remaphi; + } win[8]; + ulong dirba; /* device internal reg's base addr.: PHYSIO */ +}; + +Soc soc = { + .cpu = PHYSIO+0x20100, + .devid = PHYSIO+0x10034, + .l2cache = PHYSIO+0x20a00, /* uncachable addrs for L2 */ + .sdramc = PHYSIO+0x01400, +// .sdramd = PHYSIO+0x01500, /* unused */ + + .iocfg = PHYSIO+0x100e0, + .addrmap = PHYSIO+0x20000, + .intr = PHYSIO+0x20200, + .nand = PHYSIO+0x10418, + .cesa = PHYSIO+0x30000, /* crypto accelerator */ + .ehci = PHYSIO+0x50000, + .spi = PHYSIO+0x10600, + .twsi = PHYSIO+0x11000, + + .analog = PHYSIO+0x1007c, + .pci = PHYSIO+0x40000, + .pcibase = PHYSIO+0x41800, + + .rtc = PHYSIO+0x10300, + .clock = PHYSIO+0x20300, +// .clockctl = PHYSIO+0x1004c, /* unused */ + + .ether = { PHYSIO+0x72000, PHYSIO+0x76000, }, + .sata = { PHYSIO+0x80000, /* sata config reg here */ + PHYSIO+0x82000, /* edma config reg here */ + PHYSIO+0x84000, /* edma config reg here */ + }, + .uart = { PHYSIO+0x12000, PHYSIO+0x12100, }, + .gpio = { PHYSIO+0x10100, PHYSIO+0x10140, }, +}; + +/* + * sheeva/openrd u-boot leaves us with this address map: + * + * 0 targ 4 attr 0xe8 size 256MB addr 0x9:: remap addr 0x9:: pci mem + * 1 targ 1 attr 0x2f size 8MB addr 0xf9:: remap addr 0xf9:: nand flash + * 2 targ 4 attr 0xe0 size 16MB addr 0xf:: remap addr 0xc:: pci i/o + * 3 targ 1 attr 0x1e size 16MB addr 0xf8:: remap addr 0x0 spi flash + * 4 targ 1 attr 0x1d size 16MB addr 0xff:: boot rom + * 5 targ 1 attr 0x1e size 128MB addr 0xe8:: disabled spi flash + * 6 targ 1 attr 0x1d size 128MB addr 0xf:: disabled boot rom + * 7 targ 3 attr 0x1 size 64K addr 0xfb:: crypto sram + */ +#define WINTARG(ctl) (((ctl) >> 4) & 017) +#define WINATTR(ctl) (((ctl) >> 8) & 0377) +#define WIN64KSIZE(ctl) (((ctl) >> 16) + 1) + +static void +praddrwin(Addrwin *win, int i) +{ + ulong ctl, targ, attr, size64k; + + if (!Debug) { + USED(win, i); + return; + } + ctl = win->ctl; + targ = WINTARG(ctl); + attr = WINATTR(ctl); + size64k = WIN64KSIZE(ctl); + print("cpu addr map: %s window %d: targ %ld attr %#lux size %,ld addr %#lux", + ctl & Winenable? "enabled": "disabled", i, targ, attr, + size64k * 64*1024, win->base); + if (i < 4) + print(" remap addr %#llux", (uvlong)win->remaphi<<32 | + win->remaplo); + print("\n"); +} + +static void +fixaddrmap(void) +{ + int i; + ulong ctl, targ, attr, size64k; + Addrmap *map; + Addrwin *win; + + map = (Addrmap *)soc.addrmap; + for (i = 0; i < nelem(map->win); i++) { + win = &map->win[i]; + ctl = win->ctl; + targ = WINTARG(ctl); + attr = WINATTR(ctl); + size64k = WIN64KSIZE(ctl); + + USED(attr, size64k); + if (targ == Targcesasram) { + win->ctl |= Winenable; + win->base = PHYSCESASRAM; + coherence(); + praddrwin(win, i); + } + } + if (map->dirba != PHYSIO) + panic("dirba not %#ux", PHYSIO); +} + +static void +praddrmap(void) +{ + int i; + Addrmap *map; + + map = (Addrmap *)soc.addrmap; + for (i = 0; i < nelem(map->win); i++) + praddrwin(&map->win[i], i); +} + +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) || n == 0) + i++; + return i; +} + +void +cacheinfo(int level, int kind, Memcache *cp) /* l1 only */ +{ + uint len, assoc, size; + ulong setsways; + + /* get cache types & sizes (read-only reg) */ + setsways = cprdsc(0, CpID, CpIDidct, CpIDct); + + cp->level = level; + cp->kind = kind; + + if ((setsways & (1<<24)) == 0) + kind = Unified; + if (kind != Icache) + setsways >>= 12; + + assoc = (setsways >> 3) & MASK(3); + cp->nways = 1 << assoc; + size = (setsways >> 6) & MASK(4); + cp->size = 1 << (size + 9); + len = setsways & MASK(2); + cp->log2linelen = len + 3; + cp->linelen = 1 << cp->log2linelen; + cp->setsways = setsways; + + cp->nsets = 1 << (size + 6 - assoc - len); + cp->setsh = cp->log2linelen; + cp->waysh = 32 - log2(cp->nways); +} + +static char * +wbtype(uint type) +{ + static char *types[] = { + "write-through", + "read data block", + "reg 7 ops, no lock-down", + [06] "reg 7 ops, format A", + [07] "reg 7 ops, format B deprecated", + [016] "reg 7 ops, format C", + [05] "reg 7 ops, format D", + }; + + if (type >= nelem(types) || types[type] == nil) + return "GOK"; + return types[type]; +} + +static void +prcache(Memcache *mcp) +{ + int type; + char id; + + if (mcp->kind == Unified) + id = 'U'; + else if (mcp->kind == Icache) + id = 'I'; + else if (mcp->kind == Dcache) + id = 'D'; + else + id = '?'; + print("l%d %c: %d bytes, %d ways %d sets %d bytes/line", + mcp->level, id, mcp->size, mcp->nways, mcp->nsets, + mcp->linelen); + if (mcp->linelen != CACHELINESZ) + print(" *should* be %d", CACHELINESZ); + type = (mcp->setsways >> 25) & MASK(4); + if (type == 0) + print("; write-through only"); + else + print("; write-back type `%s' (%#o) possible", + wbtype(type), type); + if (mcp->setsways & (1<<11)) + print("; page table mapping restrictions apply"); + if (mcp->setsways & (1<<2)) + print("; M bit is set in cache type reg"); + print("\n"); +} + +static void +prcachecfg(void) +{ + Memcache mc; + + cacheinfo(1, Dcache, &mc); + prcache(&mc); + cacheinfo(1, Icache, &mc); + prcache(&mc); +} + +void +l2cacheon(void) +{ + ulong cfg; + CpucsReg *cpu; + L2uncache *l2p; + + cacheuwbinv(); + l2cacheuwbinv(); + l1cachesoff(); /* turns off L2 as a side effect */ + + cpwrsc(CpDef, CpCLD, 0, 0, 0); /* GL-CPU-100: set D cache lockdown reg. */ + + /* marvell guideline GL-CPU-130 */ + cpu = (CpucsReg *)soc.cpu; + cfg = cpu->cpucfg | L2exists | L2ecc | Cfgiprefetch | Cfgdprefetch; + + if (L2writeback) + cfg &= ~L2writethru; /* see PTE Cached & Buffered bits */ + else + cfg |= L2writethru; + cpu->l2cfg = cfg; + coherence(); /* force l2 cache to pay attention */ + cpu->l2tm1 = cpu->l2tm0 = 0x66666666; /* marvell guideline GL-CPU-120 */ + coherence(); + + cpwrsc(CpL2, CpTESTCFG, CpTCl2waylck, CpTCl2waylock, 0); + + cachedinv(); + l2cacheuinv(); + + /* disable l2 caching of i/o registers */ + l2p = (L2uncache *)soc.l2cache; + memset(l2p, 0, sizeof *l2p); + /* + * l2: don't cache upper half of address space. + * the L2 cache is PIPT, so the addresses are physical. + */ + l2p->win[0].base = 0x80000000 | L2enable; /* 64K multiple */ + l2p->win[0].size = (32*1024-1) << 16; /* 64K multiples */ + coherence(); + + l2cachecfgon(); + l1cacheson(); /* turns L2 on as a side effect */ + print("l2 cache: 256K or 512K: 4 ways, 32-byte lines, write-%s, sdram only\n", + cpu->l2cfg & L2writethru? "through": "back"); +} + +/* called late in main */ +void +archconfinit(void) +{ + m->cpuhz = Frequency; + m->delayloop = m->cpuhz/2000; /* initial estimate */ + fixaddrmap(); + if (Debug) + praddrmap(); + prcachecfg(); + + l2cacheon(); +} + +void +archkwlink(void) +{ +} + +int +archether(unsigned ctlno, Ether *ether) +{ + if(ctlno >= 2) + return -1; + ether->type = "88e1116"; + ether->port = ctlno; +// ether->mbps = 1000; + return 1; +} + +/* LED/USB gpios */ +enum { + /* + * the bit assignments are MPP pin numbers from the last page of the + * sheevaplug 6.0.1 schematic. + */ + KWOEValHigh = 1<<(49-32), /* pin 49: LED pin */ + KWOEValLow = 1<<29, /* pin 29: USB_PWEN, pin 28: usb_pwerr */ + KWOELow = ~0, + KWOEHigh = ~0, +}; + +/* called early in main */ +void +archreset(void) +{ + ulong clocks; + CpucsReg *cpu; + Dramctl *dram; + GpioReg *gpio; + + clockshutdown(); /* watchdog disabled */ + + /* configure gpios */ + gpio = (GpioReg*)soc.gpio[0]; + gpio->dataout = KWOEValLow; + coherence(); + gpio->dataoutena = KWOELow; + + gpio = (GpioReg*)soc.gpio[1]; + gpio->dataout = KWOEValHigh; + coherence(); + gpio->dataoutena = KWOEHigh; + coherence(); + + cpu = (CpucsReg *)soc.cpu; + cpu->mempm = 0; /* turn everything on */ + coherence(); + + clocks = MASK(10); + clocks |= MASK(21) & ~MASK(14); + clocks &= ~(1<<18 | 1<<1); /* reserved bits */ + cpu->clockgate |= clocks; /* enable all the clocks */ + cpu->l2cfg |= L2exists; /* when L2exists is 0, the l2 ignores us */ + coherence(); + + dram = (Dramctl *)soc.sdramc; + dram->ddrctllo &= ~(1<<6); /* marvell guideline GL-MEM-70 */ + + *(ulong *)soc.analog = 0x68; /* marvell guideline GL-MISC-40 */ + coherence(); +} + +void +archreboot(void) +{ + CpucsReg *cpu; + + iprint("reset!\n"); + delay(10); + + cpu = (CpucsReg *)soc.cpu; + cpu->rstout = RstoutSoft; + cpu->softreset = ResetSystem; + coherence(); + cpu->cpucsr = Reset; + coherence(); + delay(500); + + splhi(); + iprint("waiting..."); + for(;;) + idlehands(); +} + +void +archconsole(void) +{ +// uartconsole(0, "b115200"); +//serialputs("uart0 console @ 115200\n", strlen("uart0 console @ 115200\n")); +} + +void +archflashwp(Flash*, int) +{ +} + +int flashat(Flash *f, uintptr pa); + +/* + * 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"; + if (flashat(f, PHYSNAND1)) + f->addr = (void*)PHYSNAND1; + else if (flashat(f, PHYSNAND2)) + f->addr = (void*)PHYSNAND2; + else + f->addr = nil; + f->size = 0; /* done by probe */ + f->width = 1; + f->interleave = 0; + return 0; +} diff --git a/sys/src/9/kw/arm.h b/sys/src/9/kw/arm.h new file mode 100755 index 000000000..a9e2ff100 --- /dev/null +++ b/sys/src/9/kw/arm.h @@ -0,0 +1,194 @@ +/* + * Program Status Registers + */ +#define PsrMusr 0x00000010 /* mode */ +#define PsrMfiq 0x00000011 +#define PsrMirq 0x00000012 +#define PsrMsvc 0x00000013 +#define PsrMabt 0x00000017 +#define PsrMund 0x0000001B +#define PsrMsys 0x0000001F +#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 */ + +/* + * opcode 1 + */ +#define CpDef 0 /* default */ +#define CpL2 1 /* L2 cache operations */ + +/* + * Primary (CRn) CpSC registers. + */ +#define CpID 0 /* ID and cache type */ +#define CpCONTROL 1 /* miscellaneous control */ +#define CpTTB 2 /* Translation Table Base */ +#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 /* Cache Lockdown */ +#define CpTLD 10 /* TLB Lockdown */ +#define CpPID 13 /* Process ID */ +#define CpTESTCFG 15 /* test config. (arm926) */ + +/* + * CpID Secondary (CRm) registers. + */ +#define CpIDidct 0 +/* + * CpID op1==0 opcode2 fields. + */ +#define CpIDid 0 /* main ID */ +#define CpIDct 1 /* cache type */ + +/* + * CpCONTROL + */ +#define CpCmmu 0x00000001 /* M: MMU enable */ +#define CpCalign 0x00000002 /* A: alignment fault enable */ +#define CpCdcache 0x00000004 /* C: data cache on */ +#define CpCwb 0x00000008 /* W: write buffer turned on */ +#define CpCi32 0x00000010 /* P: 32-bit program space */ +#define CpCd32 0x00000020 /* D: 32-bit data space */ +#define CpCbe 0x00000080 /* B: big-endian operation */ +#define CpCsystem 0x00000100 /* S: system permission */ +#define CpCrom 0x00000200 /* R: ROM permission */ +#define CpCicache 0x00001000 /* I: instruction cache on */ +#define CpChv 0x00002000 /* V: high vectors */ + +/* + * CpCACHE Secondary (CRm) registers and opcode2 fields. + * In ARM-speak, 'flush' means invalidate and 'clean' means writeback. + * In arm arch v6, these must be available in user mode: + * CpCACHEinvi, CpCACHEwait (prefetch flush) + * CpCACHEwb, CpCACHEwait (DSB: data sync barrier) + * CpCACHEwb, CpCACHEdmbarr (DMB: data memory barrier) + */ +#define CpCACHEintr 0 /* interrupt */ +#define CpCACHEinvi 5 /* instruction */ +#define CpCACHEinvd 6 /* data */ +#define CpCACHEinvu 7 /* unified (I+D) */ +#define CpCACHEwb 10 /* writeback D */ +#define CpCACHEwbu 11 /* writeback U (not 926ejs) */ +#define CpCACHEwbi 14 /* writeback D + invalidate */ +#define CpCACHEwbui 15 /* writeback U + inval (not 926ejs) */ + +/* + * the 926ejs manual says that we can't use CpCACHEall nor CpCACHEwait + * for writeback operations on the 926ejs, except for CpCACHEwb + CpCACHEwait, + * which means `drain write buffer'. + */ +#define CpCACHEall 0 /* entire */ +#define CpCACHEse 1 /* single entry */ +#define CpCACHEsi 2 /* set/index */ +#define CpCACHEtest 3 /* test loop */ +#define CpCACHEwait 4 /* wait */ +#define CpCACHEdmbarr 5 /* wb: data memory barrier */ + +/* + * 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 */ + +/* + * CpTESTCFG Secondary (CRm) registers and opcode2 fields; sheeva only. + * opcode1 == CpL2 (1). L2 cache operations block the CPU until finished. + * Specifically, write-back (clean) blocks until all dirty lines have been + * drained from the L2 buffers. + */ +#define CpTCl2cfg 1 +#define CpTCl2flush 9 /* cpu blocks until flush done */ +#define CpTCl2waylck 10 +#define CpTCl2inv 11 +#define CpTCl2perfctl 12 +#define CpTCl2perfcnt 13 + +/* CpTCl2cfg */ +#define CpTCl2conf 0 + +/* CpTCl2conf bits */ +#define CpTCldcstream (1<<29) /* D cache streaming switch */ +#define CpTCl2wralloc (1<<28) /* cache write allocate */ +#define CpTCl2prefdis (1<<24) /* l2 cache prefetch disable */ +#define CpTCl2ena (1<<22) /* l2 cache enable */ + +/* CpTCl2flush & CpTCl2inv */ +#define CpTCl2all 0 +#define CpTCl2seva 1 +#define CpTCl2way 2 +#define CpTCl2sepa 3 +#define CpTCl2valow 4 +#define CpTCl2vahigh 5 /* also triggers flush or inv */ + +/* CpTCl2flush +#define CpTCecccnt 6 /* ecc error count */ +#define CpTCeccthr 7 /* ecc error threshold */ + +/* CpTCl2waylck */ +#define CpTCl2waylock 7 + +/* CpTCl2inv */ +#define CpTCl2erraddr 7 /* ecc error address */ + +/* CpTCl2perfctl */ +#define CpTCl2perf0ctl 0 +#define CpTCl2perf1ctl 1 + +/* CpTCl2perfcnt */ +#define CpTCl2perf0low 0 +#define CpTCl2perf0high 1 +#define CpTCl2perf1low 2 +#define CpTCl2perf1high 3 + +/* + * MMU page table entries. + * Mbo (0x10) bit is implementation-defined and mandatory on some pre-v7 arms. + */ +#define Mbo 0x10 /* must be 1 on earlier arms */ +#define Fault 0x00000000 /* L[12] pte: unmapped */ + +#define Coarse (Mbo|1) /* L1 */ +#define Section (Mbo|2) /* L1 1MB */ +#define Fine (Mbo|3) /* L1 */ + +#define Large 0x00000001u /* L2 64KB */ +#define Small 0x00000002u /* L2 4KB */ +#define Tiny 0x00000003u /* L2 1KB, deprecated */ +#define Buffered 0x00000004u /* L[12]: write-back not -thru */ +#define Cached 0x00000008u /* L[12] */ + +#define Dom0 0 +#define Noaccess 0 /* AP, DAC */ +#define Krw 1 /* AP */ +#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))) /* in L1, only Sections have AP */ +#define L2AP(ap) (AP(3, (ap))|AP(2, (ap))|AP(1, (ap))|AP(0, (ap))) /* pre-armv7 */ +#define DAC(n, v) F((v), (n)*2, 2) + +#define HVECTORS 0xffff0000 /* addr of vectors */ diff --git a/sys/src/9/kw/arm.s b/sys/src/9/kw/arm.s new file mode 100755 index 000000000..d48ce4a8e --- /dev/null +++ b/sys/src/9/kw/arm.s @@ -0,0 +1,72 @@ +/* + * sheevaplug machine assist, definitions + * arm926ej-s processor at 1.2GHz + * + * loader uses R11 as scratch. + */ +#include "mem.h" +#include "arm.h" + +#undef B /* B is for 'botch' */ + +#define PADDR(a) ((a) & ~KZERO) +#define KADDR(a) (KZERO|(a)) + +#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) + +/* wave at the user; clobbers R1 & R7; needs R12 (SB) set */ +#define WAVE(c) \ + ISB; \ + MOVW $PHYSCONS, R7; \ + MOVW $(c), R1; \ + MOVW R1, (R7); \ + ISB + +/* new instructions */ +#define CLZ(s, d) WORD $(0xe16f0f10 | (d) << 12 | (s)) /* count leading 0s */ + +#define DMB \ + MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEdmbarr +/* + * data synchronisation barrier (formerly drain write buffer). + * waits for cache flushes, eviction buffer drain, tlb flushes, + * branch-prediction flushes, writes to memory; the lot. + * on sheeva, also flushes L2 eviction buffer. + * zeroes R0. + */ +#define DSB \ + MOVW $0, R0; \ + MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait +/* + * prefetch flush; zeroes R0. + * arm926ej-s manual says we need to sync with l2 cache in isb, + * and uncached load is the easiest way. doesn't seem to matter. + */ +#define ISB \ + MOVW $0, R0; \ + MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEwait +// MOVW (R0), R0; MOVW $0, R0 + +/* zeroes R0 */ +#define BARRIERS ISB; DSB + +/* + * 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 */ diff --git a/sys/src/9/kw/cga.c b/sys/src/9/kw/cga.c new file mode 100755 index 000000000..f9cba1904 --- /dev/null +++ b/sys/src/9/kw/cga.c @@ -0,0 +1,132 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" + +enum { + Black, + Blue, + Green, + Cyan, + Red, + Magenta, + Brown, + Grey, + + Bright = 0x08, + Blinking = 0x80, + + Yellow = Bright|Brown, + White = Bright|Grey, +}; + +enum { + Width = 80*2, + Height = 25, + + Attr = (Black<<4)|Grey, /* high nibble background + * low foreground + */ +}; + +#define CGASCREENBASE ((uchar*)KADDR(0xB8000)) + +#define inb(x) 0 /* TODO */ +#define outb(x, y) /* TODO */ + +static int cgapos; +static Lock cgascreenlock; + +static uchar +cgaregr(int index) +{ + USED(index); + outb(0x3D4, index); + return inb(0x3D4+1) & 0xFF; +} + +static void +cgaregw(int index, int data) +{ + USED(index, data); + outb(0x3D4, index); + outb(0x3D4+1, data); +} + +static void +movecursor(void) +{ + cgaregw(0x0E, (cgapos/2>>8) & 0xFF); + cgaregw(0x0F, cgapos/2 & 0xFF); + CGASCREENBASE[cgapos+1] = Attr; +} + +static void +cgascreenputc(int c) +{ + int i; + uchar *p; + + if(c == '\n'){ + cgapos = cgapos/Width; + cgapos = (cgapos+1)*Width; + } + else if(c == '\t'){ + i = 8 - ((cgapos/2)&7); + while(i-->0) + cgascreenputc(' '); + } + else if(c == '\b'){ + if(cgapos >= 2) + cgapos -= 2; + cgascreenputc(' '); + cgapos -= 2; + } + else{ + CGASCREENBASE[cgapos++] = c; + CGASCREENBASE[cgapos++] = Attr; + } + if(cgapos >= Width*Height){ + memmove(CGASCREENBASE, &CGASCREENBASE[Width], Width*(Height-1)); + p = &CGASCREENBASE[Width*(Height-1)]; + for(i=0; i<Width/2; i++){ + *p++ = ' '; + *p++ = Attr; + } + cgapos = Width*(Height-1); + } + movecursor(); +} + +static void +cgascreenputs(char* s, int n) +{ + if(!islo()){ + /* + * Don't deadlock trying to + * print in an interrupt. + */ + if(!canlock(&cgascreenlock)) + return; + } + else + lock(&cgascreenlock); + + while(n-- > 0) + cgascreenputc(*s++); + + unlock(&cgascreenlock); +} + +void +screeninit(void) +{ + + cgapos = cgaregr(0x0E)<<8; + cgapos |= cgaregr(0x0F); + cgapos *= 2; + + screenputs = cgascreenputs; +} diff --git a/sys/src/9/kw/clock.c b/sys/src/9/kw/clock.c new file mode 100755 index 000000000..2f8bc36a1 --- /dev/null +++ b/sys/src/9/kw/clock.c @@ -0,0 +1,205 @@ +/* + * kirkwood clocks + * + * timers count down to zero. + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +#include "ureg.h" + +enum { + Tcycles = CLOCKFREQ / HZ, /* cycles per clock tick */ + Dogperiod = 15 * CLOCKFREQ, /* at most 21 s.; must fit in ulong */ + MaxPeriod = Tcycles, + MinPeriod = MaxPeriod / 100, + + /* timer ctl bits */ + Tmr0enable = 1<<0, + Tmr0reload = 1<<1, /* at 0 count, load timer0 from reload0 */ + Tmr1enable = 1<<2, + Tmr1reload = 1<<3, /* at 0 count, load timer1 from reload1 */ + TmrWDenable = 1<<4, + TmrWDreload = 1<<5, +}; + +typedef struct TimerReg TimerReg; +struct TimerReg +{ + ulong ctl; + ulong pad[3]; + ulong reload0; + ulong timer0; /* cycles until zero */ + ulong reload1; + ulong timer1; /* cycles until zero */ + ulong reloadwd; + ulong timerwd; +}; + +static int ticks; /* for sanity checking; m->ticks doesn't always get updated */ + +static void +clockintr(Ureg *ureg, void *arg) +{ + TimerReg *tmr = arg; + static int nesting; + + tmr->timerwd = Dogperiod; /* reassure the watchdog */ + ticks++; + coherence(); + + if (nesting == 0) { /* if the clock interrupted itself, bail out */ + ++nesting; + timerintr(ureg, 0); + --nesting; + } + + intrclear(Irqbridge, IRQcputimer0); +} + +/* stop clock interrupts and disable the watchdog timer */ +void +clockshutdown(void) +{ + TimerReg *tmr = (TimerReg *)soc.clock; + + tmr->ctl = 0; + coherence(); +} + +void +clockinit(void) +{ + int i, s; + CpucsReg *cpu = (CpucsReg *)soc.cpu; + TimerReg *tmr = (TimerReg *)soc.clock; + + clockshutdown(); + + /* + * verify sanity of timer0 + */ + + intrenable(Irqbridge, IRQcputimer0, clockintr, tmr, "clock0"); + s = spllo(); /* risky */ + /* take any deferred clock (& other) interrupts here */ + splx(s); + + /* adjust m->bootdelay, used by delay()? */ + m->ticks = ticks = 0; + m->fastclock = 0; + + tmr->timer0 = 1; + tmr->ctl = Tmr0enable; /* just once */ + coherence(); + + s = spllo(); /* risky */ + for (i = 0; i < 10 && ticks == 0; i++) { + delay(1); + coherence(); + } + splx(s); + if (ticks == 0) { + serialputc('?'); + if (tmr->timer0 == 0) + panic("clock not interrupting"); + else if (tmr->timer0 == tmr->reload0) + panic("clock not ticking"); + else + panic("clock running very slowly"); + } + + /* + * configure all timers + */ + clockshutdown(); + tmr->reload0 = tmr->timer0 = Tcycles; /* tick clock */ + tmr->reload1 = tmr->timer1 = ~0; /* cycle clock */ + tmr->timerwd = Dogperiod; /* watch dog timer */ + coherence(); + tmr->ctl = Tmr0enable | Tmr0reload | Tmr1enable | Tmr1reload | + TmrWDenable; + cpu->rstout |= RstoutWatchdog; + coherence(); +} + +void +timerset(Tval next) +{ + int offset; + TimerReg *tmr = (TimerReg *)soc.clock; + + offset = next - fastticks(nil); + if(offset < MinPeriod) + offset = MinPeriod; + else if(offset > MaxPeriod) + offset = MaxPeriod; + tmr->timer0 = offset; + coherence(); +} + +uvlong +fastticks(uvlong *hz) +{ + uvlong now; + int s; + + if(hz) + *hz = CLOCKFREQ; + s = splhi(); + /* zero low ulong of fastclock */ + now = (m->fastclock & ~(uvlong)~0ul) | perfticks(); + if(now < m->fastclock) /* low bits must have wrapped */ + now += 1ll << 32; + m->fastclock = now; + splx(s); + return now; +} + +ulong +perfticks(void) +{ + TimerReg *tmr = (TimerReg *)soc.clock; + + return ~tmr->timer1; +} + +long +lcycles(void) +{ + return perfticks(); +} + +ulong +µs(void) +{ + return fastticks2us(fastticks(nil)); +} + +void +microdelay(int l) +{ + int i; + + l *= m->delayloop; + l /= 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++) + ; +} diff --git a/sys/src/9/kw/coproc.c b/sys/src/9/kw/coproc.c new file mode 100755 index 000000000..6002649f1 --- /dev/null +++ b/sys/src/9/kw/coproc.c @@ -0,0 +1,153 @@ +/* + * arm co-processors + * CP15 (system control) is the one that gets used the most in practice. + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +#include "arm.h" + +#define MAP2PCSPACE(va, pc) ((uintptr)(va) & ~KSEGM | (pc) & KSEGM) + +enum { + /* alternates: 0xe12fff1e BX (R14); last e is R14 */ + /* 0xe28ef000 B 0(R14); second e is R14 (ken) */ + Retinst = 0xe1a0f00e, /* MOVW R14, R15 */ + + Fpproc = 10, /* for vfp 3+; also 11 for doubles */ +}; + +void +cpwr(int cp, int op1, int crn, int crm, int op2, ulong val) +{ + int s; + volatile ulong instr[2]; + void *pcaddr; + void (*fp)(ulong); + + s = splhi(); + op1 &= 7; + op2 &= 7; + crn &= 017; + crm &= 017; + cp &= 017; + /* MCR. Rt will be R0. */ + instr[0] = 0xee000010 | + op1 << 21 | crn << 16 | cp << 8 | op2 << 5 | crm; + instr[1] = Retinst; + coherence(); + + pcaddr = (void *)MAP2PCSPACE(instr, getcallerpc(&cp)); + cachedwbse(pcaddr, sizeof instr); + cacheiinv(); + + fp = (void (*)(ulong))pcaddr; + (*fp)(val); + coherence(); + splx(s); +} + +void +cpwrsc(int op1, int crn, int crm, int op2, ulong val) +{ + cpwr(CpSC, op1, crn, crm, op2, val); +} + +ulong +cprd(int cp, int op1, int crn, int crm, int op2) +{ + int s; + ulong res; + volatile ulong instr[2]; + void *pcaddr; + ulong (*fp)(void); + + s = splhi(); + op1 &= 7; + op2 &= 7; + crn &= 017; + crm &= 017; + /* + * MRC. return value will be in R0, which is convenient. + * Rt will be R0. + */ + instr[0] = 0xee100010 | + op1 << 21 | crn << 16 | cp << 8 | op2 << 5 | crm; + instr[1] = Retinst; + coherence(); + + pcaddr = (void *)MAP2PCSPACE(instr, getcallerpc(&cp)); + cachedwbse(pcaddr, sizeof instr); + cacheiinv(); + + fp = (ulong (*)(void))pcaddr; + res = (*fp)(); + splx(s); + return res; +} + +ulong +cprdsc(int op1, int crn, int crm, int op2) +{ + return cprd(CpSC, op1, crn, crm, op2); +} + +/* floating point */ + +ulong +fprd(int fpreg) +{ + int s; + ulong res; + volatile ulong instr[2]; + void *pcaddr; + ulong (*fp)(void); + + s = splhi(); + fpreg &= 017; + /* + * VMRS. return value will be in R0, which is convenient. + * Rt will be R0. + */ + instr[0] = 0xeef00010 | fpreg << 16 | 0 << 12 | Fpproc << 8; + instr[1] = Retinst; + coherence(); + + pcaddr = (void *)MAP2PCSPACE(instr, getcallerpc(&fpreg)); + cachedwbse(pcaddr, sizeof instr); + cacheiinv(); + + fp = (ulong (*)(void))pcaddr; + res = (*fp)(); + splx(s); + return res; +} + +void +fpwr(int fpreg, ulong val) +{ + int s; + volatile ulong instr[2]; + void *pcaddr; + void (*fp)(ulong); + + s = splhi(); + fpreg &= 017; + /* VMSR. Rt will be R0. */ + instr[0] = 0xeee00010 | fpreg << 16 | 0 << 12 | Fpproc << 8; + instr[1] = Retinst; + coherence(); + + pcaddr = (void *)MAP2PCSPACE(instr, getcallerpc(&fpreg)); + cachedwbse(pcaddr, sizeof instr); + cacheiinv(); + + fp = (void (*)(ulong))pcaddr; + (*fp)(val); + coherence(); + splx(s); +} diff --git a/sys/src/9/kw/dat.h b/sys/src/9/kw/dat.h new file mode 100755 index 000000000..88b901cf6 --- /dev/null +++ b/sys/src/9/kw/dat.h @@ -0,0 +1,320 @@ +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 struct Notsave Notsave; +typedef struct Page Page; +typedef struct Pcidev Pcidev; +typedef struct PhysUart PhysUart; +typedef struct PMMU PMMU; +typedef struct Proc Proc; +typedef u32int PTE; +typedef struct Soc Soc; +typedef struct Uart Uart; +typedef struct Ureg Ureg; +typedef uvlong Tval; + +#pragma incomplete Pcidev +#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 */ + ulong monitor; /* has monitor? */ + 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; + int socrev; /* system-on-chip revision */ + ulong delayloop; + + /* stats */ + int tlbfault; + int tlbpurge; + int pfault; + int cs; + int syscall; + int load; + int intr; + vlong 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 */ + u32int sfiq[5]; + u32int sirq[5]; + u32int sund[5]; + u32int sabt[5]; +#define fiqstack sfiq +#define irqstack sirq +#define abtstack sabt +#define undstack sund + + 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; + +enum { + Frequency = 1200*1000*1000, /* the processor clock */ +}; + +extern register Mach* m; /* R10 */ +extern register Proc* up; /* R9 */ + +extern uintptr kseg0; +extern Mach* machaddr[MAXMACH]; + +enum { + Nvec = 8, /* # of vectors at start of lexception.s */ +}; + +/* + * Layout of physical 0. + */ +typedef struct Vectorpage { + void (*vectors[Nvec])(void); + uint vtable[Nvec]; +} Vectorpage; + +/* + * 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 kind; /* I, D or unified */ + + uint size; + uint nways; /* associativity */ + uint nsets; + uint linelen; /* bytes per cache line */ + uint setsways; + + uint log2linelen; + uint waysh; /* shifts for set/way register */ + uint setsh; +}; + +struct Soc { /* addr's of SoC controllers */ + uintptr cpu; + uintptr devid; + uintptr l2cache; + uintptr sdramc; +// uintptr sdramd; /* unused */ + + uintptr iocfg; + uintptr addrmap; + uintptr intr; + uintptr nand; + uintptr cesa; /* crypto accel. */ + uintptr ehci; + uintptr spi; + uintptr twsi; + + uintptr analog; + uintptr pci; + uintptr pcibase; + + uintptr rtc; /* real-time clock */ + uintptr clock; + + uintptr ether[2]; + uintptr sata[3]; + uintptr uart[2]; + uintptr gpio[2]; +} soc; +extern Soc soc; diff --git a/sys/src/9/kw/devarch.c b/sys/src/9/kw/devarch.c new file mode 100755 index 000000000..08c76e02c --- /dev/null +++ b/sys/src/9/kw/devarch.c @@ -0,0 +1,265 @@ +#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) +{ + ulong id, archid, rev; + char *manu, *arch, *socrev; + char unk[32], socnm[32], revname[32]; + Pciex *pci; + + m->cputype = *(ulong *)soc.devid; +#ifdef OLD + switch(m->cputype & 3) { + case 0: + socnm = "88F6[12]80"; + break; + case 1: + socnm = "88F619[02]"; + break; + case 2: + socnm = "88F6281"; + break; + default: + socnm = "unknown"; + break; + } +#endif + /* strange way to get this information, but it's what u-boot does */ + pci = (Pciex *)soc.pci; + snprint(socnm, sizeof socnm, "88F%ux", pci->devid); + /* stash rev for benefit of later usb initialisation */ + m->socrev = rev = pci->revid & MASK(4); + + id = cpidget(); + if ((id >> 24) == 0x56 && pci->venid == 0x11ab) + manu = "Marvell"; + else + manu = "unknown"; + archid = (id >> 16) & MASK(4); + switch (archid) { + case 5: + arch = "v5te"; + break; + default: + snprint(unk, sizeof unk, "unknown (%ld)", archid); + arch = unk; + break; + } + if (pci->devid != 0x6281) + socrev = "unknown"; + else + switch (rev) { + case Socrevz0: + socrev = "Z0"; + break; + case Socreva0: + socrev = "A0"; + break; + case Socreva1: + socrev = "A1"; + break; + default: + snprint(revname, sizeof revname, "unknown rev (%ld)", + rev); + socrev = revname; + break; + } + seprint(buf, buf + size, + "%s %s %s; arm926ej-s arch %s rev %ld.%ld part %lux", + manu, socnm, socrev, arch, (id >> 20) & MASK(4), + id & MASK(4), (id >> 4) & MASK(12)); + 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 / 1000000); + 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); +} diff --git a/sys/src/9/kw/devether.c b/sys/src/9/kw/devether.c new file mode 100755 index 000000000..4d0a51e4c --- /dev/null +++ b/sys/src/9/kw/devether.c @@ -0,0 +1,528 @@ +#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 = ðer->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) + if(f->type == type || f->type < 0) + if(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(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; + + 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, ðer->opt[i][3]) == -1) + memset(ether->ea, 0, Eaddrlen); + }else if(cistrcmp(ether->opt[i], "fullduplex") == 0 || + cistrcmp(ether->opt[i], "10BASE-TFD") == 0) + ether->fullduplex = 1; + 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) + intrenable(Irqlo, ether->irq, ether->interrupt, + ether, 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 %#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"); + print("%s", buf); + + 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, +}; diff --git a/sys/src/9/kw/devrtc.c b/sys/src/9/kw/devrtc.c new file mode 100755 index 000000000..a47fc3033 --- /dev/null +++ b/sys/src/9/kw/devrtc.c @@ -0,0 +1,359 @@ +/* + * devrtc - real-time clock, for kirkwood + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" +#include "io.h" + +typedef struct RtcReg RtcReg; +typedef struct Rtc Rtc; + +struct RtcReg +{ + ulong time; + ulong date; + ulong alarmtm; + ulong alarmdt; + ulong intrmask; + ulong intrcause; +}; + +struct Rtc +{ + int sec; + int min; + int hour; + int wday; + int mday; + int mon; + int year; +}; + +enum { + Qdir, + Qrtc, +}; + +static Dirtab rtcdir[] = { + ".", {Qdir, 0, QTDIR}, 0, 0555, + "rtc", {Qrtc}, NUMSIZE, 0664, +}; +static RtcReg *rtcreg; /* filled in by attach */ +static Lock rtclock; + +#define SEC2MIN 60 +#define SEC2HOUR (60*SEC2MIN) +#define SEC2DAY (24L*SEC2HOUR) + +/* + * days per month plus days/year + */ +static int dmsize[] = +{ + 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; +static int ldmsize[] = +{ + 366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +/* + * return the days/month for the given year + */ +static int * +yrsize(int yr) +{ + if((yr % 4) == 0) + return ldmsize; + else + return dmsize; +} + +/* + * compute seconds since Jan 1 1970 + */ +static ulong +rtc2sec(Rtc *rtc) +{ + ulong secs; + int i; + int *d2m; + + /* + * seconds per year + */ + secs = 0; + for(i = 1970; i < rtc->year; i++){ + d2m = yrsize(i); + secs += d2m[0] * SEC2DAY; + } + + /* + * seconds per month + */ + d2m = yrsize(rtc->year); + for(i = 1; i < rtc->mon; i++) + secs += d2m[i] * SEC2DAY; + + secs += (rtc->mday-1) * SEC2DAY; + secs += rtc->hour * SEC2HOUR; + secs += rtc->min * SEC2MIN; + secs += rtc->sec; + + return secs; +} + +/* + * compute rtc from seconds since Jan 1 1970 + */ +static void +sec2rtc(ulong secs, Rtc *rtc) +{ + int d; + long hms, day; + int *d2m; + + /* + * break initial number into days + */ + hms = secs % SEC2DAY; + day = secs / SEC2DAY; + if(hms < 0) { + hms += SEC2DAY; + day -= 1; + } + + /* + * 19700101 was thursday + */ + rtc->wday = (day + 7340036L) % 7; + + /* + * generate hours:minutes:seconds + */ + rtc->sec = hms % 60; + d = hms / 60; + rtc->min = d % 60; + d /= 60; + rtc->hour = d; + + /* + * year number + */ + if(day >= 0) + for(d = 1970; day >= *yrsize(d); d++) + day -= *yrsize(d); + else + for (d = 1970; day < 0; d--) + day += *yrsize(d-1); + rtc->year = d; + + /* + * generate month + */ + d2m = yrsize(rtc->year); + for(d = 1; day >= d2m[d]; d++) + day -= d2m[d]; + rtc->mday = day + 1; + rtc->mon = d; +} + +enum { + Rtcsec = 0x00007f, + Rtcmin = 0x007f00, + Rtcms = 8, + Rtchr12 = 0x1f0000, + Rtchr24 = 0x3f0000, + Rtchrs = 16, + + Rdmday = 0x00003f, + Rdmon = 0x001f00, + Rdms = 8, + Rdyear = 0x7f0000, + Rdys = 16, + + Rtcpm = 1<<21, /* pm bit */ + Rtc12 = 1<<22, /* 12 hr clock */ +}; + +static ulong +bcd2dec(ulong bcd) +{ + ulong d, m, i; + + d = 0; + m = 1; + for(i = 0; i < 2 * sizeof d; i++){ + d += ((bcd >> (4*i)) & 0xf) * m; + m *= 10; + } + return d; +} + +static ulong +dec2bcd(ulong d) +{ + ulong bcd, i; + + bcd = 0; + for(i = 0; d != 0; i++){ + bcd |= (d%10) << (4*i); + d /= 10; + } + return bcd; +} + +static long +_rtctime(void) +{ + ulong t, d; + Rtc rtc; + + t = rtcreg->time; + d = rtcreg->date; + + rtc.sec = bcd2dec(t & Rtcsec); + rtc.min = bcd2dec((t & Rtcmin) >> Rtcms); + + if(t & Rtc12){ + rtc.hour = bcd2dec((t & Rtchr12) >> Rtchrs) - 1; /* 1—12 */ + if(t & Rtcpm) + rtc.hour += 12; + }else + rtc.hour = bcd2dec((t & Rtchr24) >> Rtchrs); /* 0—23 */ + + rtc.mday = bcd2dec(d & Rdmday); /* 1—31 */ + rtc.mon = bcd2dec((d & Rdmon) >> Rdms); /* 1—12 */ + rtc.year = bcd2dec((d & Rdyear) >> Rdys) + 2000; /* year%100 */ + +// print("%0.2d:%0.2d:%.02d %0.2d/%0.2d/%0.2d\n", /* HH:MM:SS YY/MM/DD */ +// rtc.hour, rtc.min, rtc.sec, rtc.year, rtc.mon, rtc.mday); + return rtc2sec(&rtc); +} + +long +rtctime(void) +{ + int i; + long t, ot; + + ilock(&rtclock); + + /* loop until we get two reads in a row the same */ + t = _rtctime(); + ot = ~t; + for(i = 0; i < 100 && ot != t; i++){ + ot = t; + t = _rtctime(); + } + if(ot != t) + print("rtctime: we are boofheads\n"); + + iunlock(&rtclock); + return t; +} + +static void +setrtc(Rtc *rtc) +{ + ilock(&rtclock); + rtcreg->time = dec2bcd(rtc->wday) << 24 | dec2bcd(rtc->hour) << 16 | + dec2bcd(rtc->min) << 8 | dec2bcd(rtc->sec); + rtcreg->date = dec2bcd(rtc->year - 2000) << 16 | + dec2bcd(rtc->mon) << 8 | dec2bcd(rtc->mday); + iunlock(&rtclock); +} + +static Chan* +rtcattach(char *spec) +{ + rtcreg = (RtcReg*)soc.rtc; + return devattach(L'r', spec); +} + +static Walkqid* +rtcwalk(Chan *c, Chan *nc, char **name, int nname) +{ + return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen); +} + +static int +rtcstat(Chan *c, uchar *dp, int n) +{ + return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen); +} + +static Chan* +rtcopen(Chan *c, int omode) +{ + return devopen(c, omode, rtcdir, nelem(rtcdir), devgen); +} + +static void +rtcclose(Chan*) +{ +} + +static long +rtcread(Chan *c, void *buf, long n, vlong off) +{ + if(c->qid.type & QTDIR) + return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen); + + switch((ulong)c->qid.path){ + default: + error(Egreg); + case Qrtc: + return readnum(off, buf, n, rtctime(), NUMSIZE); + } +} + +static long +rtcwrite(Chan *c, void *buf, long n, vlong off) +{ + ulong offset = off; + char *cp, sbuf[32]; + Rtc rtc; + + switch((ulong)c->qid.path){ + default: + error(Egreg); + case Qrtc: + if(offset != 0 || n >= sizeof(sbuf)-1) + error(Ebadarg); + memmove(sbuf, buf, n); + sbuf[n] = '\0'; + for(cp = sbuf; *cp != '\0'; cp++) + if(*cp >= '0' && *cp <= '9') + break; + sec2rtc(strtoul(cp, 0, 0), &rtc); + setrtc(&rtc); + return n; + } +} + +Dev rtcdevtab = { + L'r', + "rtc", + + devreset, + devinit, + devshutdown, + rtcattach, + rtcwalk, + rtcstat, + rtcopen, + devcreate, + rtcclose, + rtcread, + devbread, + rtcwrite, + devbwrite, + devremove, + devwstat, + devpower, +}; diff --git a/sys/src/9/kw/devtwsi.c b/sys/src/9/kw/devtwsi.c new file mode 100755 index 000000000..ec4a268c9 --- /dev/null +++ b/sys/src/9/kw/devtwsi.c @@ -0,0 +1,305 @@ +/* + * kirkwood two-wire serial interface (TWSI) and + * inter-integrated circuit (IC) driver + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" +#include "io.h" + +enum { + Qdir, + Qtwsi, +}; + +typedef struct Kwtwsi Kwtwsi; +typedef struct Twsi Twsi; + +struct Kwtwsi { /* device registers */ + ulong saddr; + ulong data; + ulong ctl; + union { + ulong status; /* ro */ + ulong rate; /* wo: baud rate */ + }; + + ulong saddrext; + uchar _pad0[0x1c-0x14]; + ulong reset; + uchar _pad1[0x98-0x20]; + ulong initlastdata; +}; + +enum { + Twsidowrite, + Twsidoread, + + /* ctl bits */ + Twsiack = 1<<2, /* recv'd data; clear to ack */ + Twsiint = 1<<3, /* interrupt conditions true */ + Twsistop = 1<<4, + Twsistart = 1<<5, + Twsislaveen = 1<<6, + Twsiinten = 1<<7, /* interrupts enabled */ + + /* status codes */ + SStart = 0x08, + SWa = 0x18, + SWda = 0x28, + SRa = 0x40, + SRda = 0x50, + SRna = 0x58, +}; + +struct Twsi { + QLock; + Rendez nextbyte; + + /* remainder is state needed to track the operation in progress */ + int intr; + int done; + + uchar *bp; /* current ptr into buf */ + uchar *end; + + ulong addr; /* device address */ + char *error; +}; + +static Twsi twsi; + +static Dirtab twsidir[] = { + ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555, + "twsi", {Qtwsi}, 0, 0660, +}; + +static char Eabsts[] = "abnormal status"; + +static void +twsifinish(void) +{ + Kwtwsi *krp = (Kwtwsi *)soc.twsi; + + twsi.done = 1; + krp->ctl |= Twsistop; + coherence(); +} + +static void +twsidoread(void) +{ + Kwtwsi *krp = (Kwtwsi *)soc.twsi; + + switch(krp->status){ + case SStart: + krp->data = twsi.addr << 1 | Twsidoread; + break; + case SRa: + krp->ctl |= Twsiack; + break; + case SRda: + if(twsi.bp < twsi.end) { + *twsi.bp++ = krp->data; + krp->ctl |= Twsiack; + } else + krp->ctl &= ~Twsiack; + break; + case SRna: + twsifinish(); + break; + default: + twsifinish(); + twsi.error = Eabsts; + break; + } +} + +static void +twsidowrite(void) +{ + Kwtwsi *krp = (Kwtwsi *)soc.twsi; + + switch(krp->status){ + case SStart: + krp->data = twsi.addr << 1 | Twsidowrite; + break; + case SWa: + case SWda: + if(twsi.bp < twsi.end) + krp->data = *twsi.bp++; + else + twsifinish(); + break; + default: + twsifinish(); + twsi.error = Eabsts; + break; + } +} + +static int +twsigotintr(void *) +{ + return twsi.intr; +} + +static long +twsixfer(uchar *buf, ulong len, ulong offset, void (*op)(void)) +{ + ulong off; + char *err; + Kwtwsi *krp = (Kwtwsi *)soc.twsi; + + qlock(&twsi); + twsi.bp = buf; + twsi.end = buf + len; + + twsi.addr = offset; + twsi.done = twsi.intr = 0; + twsi.error = nil; + + krp->ctl = (krp->ctl & ~Twsiint) | Twsistart; + coherence(); + while (!twsi.done) { + sleep(&twsi.nextbyte, twsigotintr, 0); + twsi.intr = 0; + (*op)(); + /* signal to start new op & extinguish intr source */ + krp->ctl &= ~Twsiint; + coherence(); + krp->ctl |= Twsiinten; + coherence(); + } + twsifinish(); + err = twsi.error; + off = twsi.bp - buf; + twsi.bp = nil; /* prevent accidents */ + qunlock(&twsi); + + if(err) + error(err); + return off; +} + +static void +interrupt(Ureg *, void *) +{ + Kwtwsi *krp = (Kwtwsi *)soc.twsi; + + twsi.intr = 1; + wakeup(&twsi.nextbyte); + + krp->ctl &= ~Twsiinten; /* stop further interrupts */ + coherence(); + intrclear(Irqlo, IRQ0twsi); +} + +static void +twsiinit(void) +{ + Kwtwsi *krp = (Kwtwsi *)soc.twsi; + + intrenable(Irqlo, IRQ0twsi, interrupt, nil, "twsi"); + krp->ctl &= ~Twsiint; + krp->ctl |= Twsiinten; + coherence(); +} + +static void +twsishutdown(void) +{ + Kwtwsi *krp = (Kwtwsi *)soc.twsi; + + krp->ctl &= ~Twsiinten; + coherence(); + intrdisable(Irqlo, IRQ0twsi, interrupt, nil, "twsi"); +} + +static Chan* +twsiattach(char *param) +{ + return devattach(L'', param); +} + +static Walkqid* +twsiwalk(Chan *c, Chan *nc, char **name, int nname) +{ + return devwalk(c, nc, name, nname, twsidir, nelem(twsidir), devgen); +} + +static int +twsistat(Chan *c, uchar *db, int n) +{ + return devstat(c, db, n, twsidir, nelem(twsidir), devgen); +} + +static Chan* +twsiopen(Chan *c, int omode) +{ + switch((ulong)c->qid.path){ + default: + error(Eperm); + case Qdir: + case Qtwsi: + break; + } + c = devopen(c, omode, twsidir, nelem(twsidir), devgen); + c->mode = openmode(omode); + c->flag |= COPEN; + c->offset = 0; + return c; +} + +static void +twsiclose(Chan *) +{ +} + +static long +twsiread(Chan *c, void *v, long n, vlong off) +{ + switch((ulong)c->qid.path){ + default: + error(Eperm); + case Qdir: + return devdirread(c, v, n, twsidir, nelem(twsidir), devgen); + case Qtwsi: + return twsixfer(v, n, off, twsidoread); + } +} + +static long +twsiwrite(Chan *c, void *v, long n, vlong off) +{ + switch((ulong)c->qid.path){ + default: + error(Eperm); + case Qtwsi: + return twsixfer(v, n, off, twsidowrite); + } +} + +Dev twsidevtab = { + L'', + "twsi", + + devreset, + twsiinit, + twsishutdown, + twsiattach, + twsiwalk, + twsistat, + twsiopen, + devcreate, + twsiclose, + twsiread, + devbread, + twsiwrite, + devbwrite, + devremove, + devwstat, +}; diff --git a/sys/src/9/kw/devusb.c b/sys/src/9/kw/devusb.c new file mode 100755 index 000000000..de5826165 --- /dev/null +++ b/sys/src/9/kw/devusb.c @@ -0,0 +1,1460 @@ +/* + * USB device driver framework. + * + * 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 "../port/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; +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 = smalloc(sizeof(Ep)); + 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 = smalloc(sizeof(Udev)); + 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 = smalloc(sizeof(Hci)); + hp->ctlrno = ctlrno; + hp->tbdf = BUSUNKNOWN; + + 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(Irqlo, hp->irq, hp->interrupt, hp, name); + print("#u/usb/ep%d.0: %s: port %#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) +{ + return 1 << n; +} + +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) +{ + int i, l, mode, nb, tt; + char *b, *s; + Cmdbuf *cb; + Cmdtab *ct; + Ep *nep; + Udev *d; + static char *Info = "info "; + + 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 nr, q; + Ep *ep; + + 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, +}; diff --git a/sys/src/9/kw/ether1116.c b/sys/src/9/kw/ether1116.c new file mode 100755 index 000000000..b60c70a49 --- /dev/null +++ b/sys/src/9/kw/ether1116.c @@ -0,0 +1,1747 @@ +/* + * marvell kirkwood gigabit ethernet (88e1116 and 88e1121) driver + * (as found in the sheevaplug, openrd and guruplug). + * the main difference is the flavour of phy kludgery necessary. + * + * from /public/doc/marvell/88f61xx.kirkwood.pdf, + * /public/doc/marvell/88e1116.pdf, and + * /public/doc/marvell/88e1121r.pdf. + */ + +#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" +#include "ethermii.h" +#include "../ip/ip.h" + +#define MIIDBG if(0)iprint + +#define WINATTR(v) (((v) & MASK(8)) << 8) +#define WINSIZE(v) (((v)/(64*1024) - 1) << 16) + +enum { + Nrx = 512, + Ntx = 32, + Nrxblks = 1024, + Rxblklen = 2+1522, /* ifc. supplies first 2 bytes as padding */ + + Maxrxintrsec = 20*1000, /* max. rx intrs. / sec */ + Etherstuck = 70, /* must send or receive a packet in this many sec.s */ + + Descralign = 16, + Bufalign = 8, + + Pass = 1, /* accept packets */ + + Qno = 0, /* do everything on queue zero */ +}; + +typedef struct Ctlr Ctlr; +typedef struct Gbereg Gbereg; +typedef struct Mibstats Mibstats; +typedef struct Rx Rx; +typedef struct Tx Tx; + +static struct { + Lock; + Block *head; +} freeblocks; + +/* hardware receive buffer descriptor */ +struct Rx { + ulong cs; + ulong countsize; /* bytes, buffer size */ + ulong buf; /* phys. addr. of packet buffer */ + ulong next; /* phys. addr. of next Rx */ +}; + +/* hardware transmit buffer descriptor */ +struct Tx { + ulong cs; + ulong countchk; /* bytes, checksum */ + ulong buf; /* phys. addr. of packet buffer */ + ulong next; /* phys. addr. of next Tx */ +}; + +/* fixed by hw; part of Gberegs */ +struct Mibstats { + union { + uvlong rxby; /* good bytes rcv'd */ + struct { + ulong rxbylo; + ulong rxbyhi; + }; + }; + ulong badrxby; /* bad bytes rcv'd */ + ulong mactxerr; /* tx err pkts */ + ulong rxpkt; /* good pkts rcv'd */ + ulong badrxpkt; /* bad pkts rcv'd */ + ulong rxbcastpkt; /* b'cast pkts rcv'd */ + ulong rxmcastpkt; /* m'cast pkts rcv'd */ + + ulong rx64; /* pkts <= 64 bytes */ + ulong rx65_127; /* pkts 65—127 bytes */ + ulong rx128_255; /* pkts 128—255 bytes */ + ulong rx256_511; /* pkts 256—511 bytes */ + ulong rx512_1023; /* pkts 512—1023 bytes */ + ulong rx1024_max; /* pkts >= 1024 bytes */ + + union { + uvlong txby; /* good bytes sent */ + struct { + ulong txbylo; + ulong txbyhi; + }; + }; + ulong txpkt; /* good pkts sent */ + /* half-duplex: pkts dropped due to excessive collisions */ + ulong txcollpktdrop; + ulong txmcastpkt; /* m'cast pkts sent */ + ulong txbcastpkt; /* b'cast pkts sent */ + + ulong badmacctlpkts; /* bad mac ctl pkts */ + ulong txflctl; /* flow-control pkts sent */ + ulong rxflctl; /* good flow-control pkts rcv'd */ + ulong badrxflctl; /* bad flow-control pkts rcv'd */ + + ulong rxundersized; /* runts */ + ulong rxfrags; /* fragments rcv'd */ + ulong rxtoobig; /* oversized pkts rcv'd */ + ulong rxjabber; /* jabber pkts rcv'd */ + ulong rxerr; /* rx error events */ + ulong crcerr; /* crc error events */ + ulong collisions; /* collision events */ + ulong latecoll; /* late collisions */ +}; + +struct Ctlr { + Lock; + Ether *ether; + Gbereg *reg; + + Lock initlock; + int init; + + Rx *rx; /* receive descriptors */ + Block *rxb[Nrx]; /* blocks belonging to the descriptors */ + int rxhead; /* descr ethernet will write to next */ + int rxtail; /* next descr that might need a buffer */ + Rendez rrendez; /* interrupt wakes up read process */ + int haveinput; + + Tx *tx; + Block *txb[Ntx]; + int txhead; /* next descr we can use for new packet */ + int txtail; /* next descr to reclaim on tx complete */ + + Mii *mii; + int port; + + /* stats */ + ulong intrs; + ulong newintrs; + ulong txunderrun; + ulong txringfull; + ulong rxdiscard; + ulong rxoverrun; + ulong nofirstlast; + + Mibstats; +}; + +#define Rxqon(q) (1<<(q)) +#define Txqon(q) (1<<(q)) + +enum { + /* euc bits */ + Portreset = 1 << 20, + + /* sdma config, sdc bits */ + Burst1 = 0, + Burst2, + Burst4, + Burst8, + Burst16, + SDCrifb = 1<<0, /* rx intr on pkt boundaries */ +#define SDCrxburst(v) ((v)<<1) + SDCrxnobyteswap = 1<<4, + SDCtxnobyteswap = 1<<5, + SDCswap64byte = 1<<6, +#define SDCtxburst(v) ((v)<<22) + /* rx intr ipg (inter packet gap) */ +#define SDCipgintrx(v) ((((v)>>15) & 1)<<25) | (((v) & MASK(15))<<7) + + /* portcfg bits */ + PCFGupromisc = 1<<0, /* unicast promiscuous mode */ +#define Rxqdefault(q) ((q)<<1) +#define Rxqarp(q) ((q)<<4) + PCFGbcrejectnoiparp = 1<<7, + PCFGbcrejectip = 1<<8, + PCFGbcrejectarp = 1<<9, + PCFGamnotxes = 1<<12, /* auto mode, no summary update on tx */ + PCFGtcpq = 1<<14, /* capture tcp frames to tcpq */ + PCFGudpq = 1<<15, /* capture udp frames to udpq */ +#define Rxqtcp(q) ((q)<<16) +#define Rxqudp(q) ((q)<<19) +#define Rxqbpdu(q) ((q)<<22) + PCFGrxcs = 1<<25, /* rx tcp checksum mode with header */ + + /* portcfgx bits */ + PCFGXspanq = 1<<1, + PCFGXcrcoff = 1<<2, /* no ethernet crc */ + + /* port serial control0, psc0 bits */ + PSC0porton = 1<<0, + PSC0forcelinkup = 1<<1, + PSC0an_dplxoff = 1<<2, /* an_ = auto. negotiate */ + PSC0an_flctloff = 1<<3, + PSC0an_pauseadv = 1<<4, + PSC0nofrclinkdown = 1<<10, + PSC0an_spdoff = 1<<13, + PSC0dteadv = 1<<14, /* dte advertise */ + + /* max. input pkt size */ +#define PSC0mru(v) ((v)<<17) + PSC0mrumask = PSC0mru(MASK(3)), + PSC0mru1518 = 0, /* 1500+2* 6(addrs) +2 + 4(crc) */ + PSC0mru1522, /* 1518 + 4(vlan tags) */ + PSC0mru1552, /* `baby giant' */ + PSC0mru9022, /* `jumbo' */ + PSC0mru9192, /* bigger jumbo */ + PSC0mru9700, /* still bigger jumbo */ + + PSC0fd_frc = 1<<21, /* force full duplex */ + PSC0flctlfrc = 1<<22, + PSC0gmiispd_gbfrc = 1<<23, + PSC0miispdfrc100mbps = 1<<24, + + /* port status 0, ps0 bits */ + PS0linkup = 1<<1, + PS0fd = 1<<2, /* full duplex */ + PS0flctl = 1<<3, + PS0gmii_gb = 1<<4, + PS0mii100mbps = 1<<5, + PS0txbusy = 1<<7, + PS0txfifoempty = 1<<10, + PS0rxfifo1empty = 1<<11, + PS0rxfifo2empty = 1<<12, + + /* port serial control 1, psc1 bits */ + PSC1loopback = 1<<1, + PSC1mii = 0<<2, + PSC1rgmii = 1<<3, /* enable RGMII */ + PSC1portreset = 1<<4, + PSC1clockbypass = 1<<5, + PSC1iban = 1<<6, + PSC1iban_bypass = 1<<7, + PSC1iban_restart= 1<<8, + PSC1_gbonly = 1<<11, + PSC1encolonbp = 1<<15, /* "collision during back-pressure mib counting" */ + PSC1coldomlimmask= MASK(6)<<16, +#define PSC1coldomlim(v) (((v) & MASK(6))<<16) + PSC1miiallowoddpreamble = 1<<22, + + /* port status 1, ps1 bits */ + PS1rxpause = 1<<0, + PS1txpause = 1<<1, + PS1pressure = 1<<2, + PS1syncfail10ms = 1<<3, + PS1an_done = 1<<4, + PS1inbandan_bypassed = 1<<5, + PS1serdesplllocked = 1<<6, + PS1syncok = 1<<7, + PS1nosquelch = 1<<8, + + /* irq bits */ + /* rx buf returned to cpu ownership, or frame reception finished */ + Irx = 1<<0, + Iextend = 1<<1, /* IEsum of irqe set */ +#define Irxbufferq(q) (1<<((q)+2)) /* rx buf returned to cpu ownership */ + Irxerr = 1<<10, /* input ring full, usually */ +#define Irxerrq(q) (1<<((q)+11)) +#define Itxendq(q) (1<<((q)+19)) /* tx dma stopped for q */ + Isum = 1<<31, + + /* irq extended, irqe bits */ +#define IEtxbufferq(q) (1<<((q)+0)) /* tx buf returned to cpu ownership */ +#define IEtxerrq(q) (1<<((q)+8)) + IEphystschg = 1<<16, + IEptp = 1<<17, + IErxoverrun = 1<<18, + IEtxunderrun = 1<<19, + IElinkchg = 1<<20, + IEintaddrerr = 1<<23, + IEprbserr = 1<<25, + IEsum = 1<<31, + + /* tx fifo urgent threshold (tx interrupt coalescing), pxtfut */ +#define TFUTipginttx(v) (((v) & MASK(16))<<4); + + /* minimal frame size, mfs */ + MFS40by = 10<<2, + MFS44by = 11<<2, + MFS48by = 12<<2, + MFS52by = 13<<2, + MFS56by = 14<<2, + MFS60by = 15<<2, + MFS64by = 16<<2, + + /* receive descriptor status */ + RCSmacerr = 1<<0, + RCSmacmask = 3<<1, + RCSmacce = 0<<1, + RCSmacor = 1<<1, + RCSmacmf = 2<<1, + RCSl4chkshift = 3, + RCSl4chkmask = MASK(16), + RCSvlan = 1<<17, + RCSbpdu = 1<<18, + RCSl4mask = 3<<21, + RCSl4tcp4 = 0<<21, + RCSl4udp4 = 1<<21, + RCSl4other = 2<<21, + RCSl4rsvd = 3<<21, + RCSl2ev2 = 1<<23, + RCSl3ip4 = 1<<24, + RCSip4headok = 1<<25, + RCSlast = 1<<26, + RCSfirst = 1<<27, + RCSunknownaddr = 1<<28, + RCSenableintr = 1<<29, + RCSl4chkok = 1<<30, + RCSdmaown = 1<<31, + + /* transmit descriptor status */ + TCSmacerr = 1<<0, + TCSmacmask = 3<<1, + TCSmaclc = 0<<1, + TCSmacur = 1<<1, + TCSmacrl = 2<<1, + TCSllc = 1<<9, + TCSl4chkmode = 1<<10, + TCSipv4hdlenshift= 11, + TCSvlan = 1<<15, + TCSl4type = 1<<16, + TCSgl4chk = 1<<17, + TCSgip4chk = 1<<18, + TCSpadding = 1<<19, + TCSlast = 1<<20, + TCSfirst = 1<<21, + TCSenableintr = 1<<23, + TCSautomode = 1<<30, + TCSdmaown = 1<<31, +}; + +enum { + /* SMI regs */ + PhysmiTimeout = 10000, /* what units? in ms. */ + Physmidataoff = 0, /* Data */ + Physmidatamask = 0xffff<<Physmidataoff, + + Physmiaddroff = 16, /* PHY device addr */ + Physmiaddrmask = 0x1f << Physmiaddroff, + + Physmiop = 26, + Physmiopmask = 3<<Physmiop, + PhysmiopWr = 0<<Physmiop, + PhysmiopRd = 1<<Physmiop, + + PhysmiReadok = 1<<27, + PhysmiBusy = 1<<28, + + SmiRegaddroff = 21, /* PHY device register addr */ + SmiRegaddrmask = 0x1f << SmiRegaddroff, +}; + +struct Gbereg { + ulong phy; /* PHY address */ + ulong smi; /* serial mgmt. interface */ + ulong euda; /* ether default address */ + ulong eudid; /* ether default id */ + uchar _pad0[0x80-0x10]; + + /* dma stuff */ + ulong euirq; /* interrupt cause */ + ulong euirqmask; /* interrupt mask */ + uchar _pad1[0x94-0x88]; + ulong euea; /* error address */ + ulong euiae; /* internal error address */ + uchar _pad2[0xb0-0x9c]; + ulong euc; /* control */ + uchar _pad3[0x200-0xb4]; + struct { + ulong base; /* window base */ + ulong size; /* window size */ + } base[6]; + uchar _pad4[0x280-0x230]; + ulong harr[4]; /* high address remap */ + ulong bare; /* base address enable */ + ulong epap; /* port access protect */ + uchar _pad5[0x400-0x298]; + + ulong portcfg; /* port configuration */ + ulong portcfgx; /* port config. extend */ + ulong mii; /* mii serial parameters */ + ulong _pad6; + ulong evlane; /* vlan ether type */ + ulong macal; /* mac address low */ + ulong macah; /* mac address high */ + ulong sdc; /* sdma config. */ + ulong dscp[7]; /* ip diff. serv. code point -> pri */ + ulong psc0; /* port serial control 0 */ + ulong vpt2p; /* vlan priority tag -> pri */ + ulong ps0; /* ether port status 0 */ + ulong tqc; /* transmit queue command */ + ulong psc1; /* port serial control 1 */ + ulong ps1; /* ether port status 1 */ + ulong mvhdr; /* marvell header */ + ulong _pad8[2]; + + /* interrupts */ + ulong irq; /* interrupt cause; some rw0c bits */ + ulong irqe; /* " " extended; some rw0c bits */ + ulong irqmask; /* interrupt mask (actually enable) */ + ulong irqemask; /* " " extended */ + + ulong _pad9; + ulong pxtfut; /* port tx fifo urgent threshold */ + ulong _pad10; + ulong pxmfs; /* port rx minimum frame size */ + ulong _pad11; + + /* + * # of input frames discarded by addr filtering or lack of resources; + * zeroed upon read. + */ + ulong pxdfc; /* port rx discard frame counter */ + ulong pxofc; /* port overrun frame counter */ + ulong _pad12[2]; + ulong piae; /* port internal address error */ + uchar _pad13[0x4bc-0x498]; + ulong etherprio; /* ether type priority */ + uchar _pad14[0x4dc-0x4c0]; + ulong tqfpc; /* tx queue fixed priority config. */ + ulong pttbrc; /* port tx token-bucket rate config. */ + ulong tqc1; /* tx queue command 1 */ + ulong pmtu; /* port maximum transmit unit */ + ulong pmtbs; /* port maximum token bucket size */ + uchar _pad15[0x600-0x4f0]; + + struct { + ulong _pad[3]; + ulong r; /* phys. addr.: cur. rx desc. ptrs */ + } crdp[8]; + ulong rqc; /* rx queue command */ + ulong tcsdp; /* phys. addr.: cur. tx desc. ptr */ + uchar _pad16[0x6c0-0x688]; + + ulong tcqdp[8]; /* phys. addr.: cur. tx q. desc. ptr */ + uchar _pad17[0x700-0x6e0]; + + struct { + ulong tbctr; /* queue tx token-bucket counter */ + ulong tbcfg; /* tx queue token-bucket config. */ + ulong acfg; /* tx queue arbiter config. */ + ulong _pad; + } tq[8]; + ulong pttbc; /* port tx token-bucket counter */ + uchar _pad18[0x7a8-0x784]; + + ulong ipg2; /* tx queue ipg */ + ulong _pad19[3]; + ulong ipg3; + ulong _pad20; + ulong htlp; /* high token in low packet */ + ulong htap; /* high token in async packet */ + ulong ltap; /* low token in async packet */ + ulong _pad21; + ulong ts; /* tx speed */ + uchar _pad22[0x1000-0x7d4]; + + /* mac mib counters: statistics */ + Mibstats; + uchar _pad23[0x1400-0x1080]; + + /* multicast filtering; each byte: Qno<<1 | Pass */ + ulong dfsmt[64]; /* dest addr filter special m'cast table */ + ulong dfomt[64]; /* dest addr filter other m'cast table */ + /* unicast filtering */ + ulong dfut[4]; /* dest addr filter unicast table */ +}; + +static Ctlr *ctlrs[MaxEther]; +static uchar zeroea[Eaddrlen]; + +static void getmibstats(Ctlr *); + +static void +rxfreeb(Block *b) +{ + /* freeb(b) will have previously decremented b->ref to 0; raise to 1 */ + _xinc(&b->ref); + b->wp = b->rp = + (uchar*)((uintptr)(b->lim - Rxblklen) & ~(Bufalign - 1)); + assert(((uintptr)b->rp & (Bufalign - 1)) == 0); + b->free = rxfreeb; + + ilock(&freeblocks); + b->next = freeblocks.head; + freeblocks.head = b; + iunlock(&freeblocks); +} + +static Block * +rxallocb(void) +{ + Block *b; + + ilock(&freeblocks); + b = freeblocks.head; + if(b != nil) { + freeblocks.head = b->next; + b->next = nil; + b->free = rxfreeb; + } + iunlock(&freeblocks); + return b; +} + +static void +rxkick(Ctlr *ctlr) +{ + Gbereg *reg = ctlr->reg; + + if (reg->crdp[Qno].r == 0) + reg->crdp[Qno].r = PADDR(&ctlr->rx[ctlr->rxhead]); + if ((reg->rqc & 0xff) == 0) /* all queues are stopped? */ + reg->rqc = Rxqon(Qno); /* restart */ + coherence(); +} + +static void +txkick(Ctlr *ctlr) +{ + Gbereg *reg = ctlr->reg; + + if (reg->tcqdp[Qno] == 0) + reg->tcqdp[Qno] = PADDR(&ctlr->tx[ctlr->txhead]); + if ((reg->tqc & 0xff) == 0) /* all q's stopped? */ + reg->tqc = Txqon(Qno); /* restart */ + coherence(); +} + +static void +rxreplenish(Ctlr *ctlr) +{ + Rx *r; + Block *b; + + while(ctlr->rxb[ctlr->rxtail] == nil) { + b = rxallocb(); + if(b == nil) { + iprint("#l%d: rxreplenish out of buffers\n", + ctlr->ether->ctlrno); + break; + } + + ctlr->rxb[ctlr->rxtail] = b; + + /* set up uncached receive descriptor */ + r = &ctlr->rx[ctlr->rxtail]; + assert(((uintptr)r & (Descralign - 1)) == 0); + r->countsize = ROUNDUP(Rxblklen, 8); + r->buf = PADDR(b->rp); + coherence(); + + /* and fire */ + r->cs = RCSdmaown | RCSenableintr; + coherence(); + + ctlr->rxtail = NEXT(ctlr->rxtail, Nrx); + } +} + +static void +dump(uchar *bp, long max) +{ + if (max > 64) + max = 64; + for (; max > 0; max--, bp++) + iprint("%02.2ux ", *bp); + print("...\n"); +} + +static void +etheractive(Ether *ether) +{ + ether->starttime = TK2MS(MACHP(0)->ticks)/1000; +} + +static void +ethercheck(Ether *ether) +{ + if (ether->starttime != 0 && + TK2MS(MACHP(0)->ticks)/1000 - ether->starttime > Etherstuck) { + etheractive(ether); + if (ether->ctlrno == 0) /* only complain about main ether */ + iprint("#l%d: ethernet stuck\n", ether->ctlrno); + } +} + +static void +receive(Ether *ether) +{ + int i; + ulong n; + Block *b; + Ctlr *ctlr = ether->ctlr; + Rx *r; + + ethercheck(ether); + for (i = Nrx-2; i > 0; i--) { + r = &ctlr->rx[ctlr->rxhead]; /* *r is uncached */ + assert(((uintptr)r & (Descralign - 1)) == 0); + if(r->cs & RCSdmaown) /* descriptor busy? */ + break; + + b = ctlr->rxb[ctlr->rxhead]; /* got input buffer? */ + if (b == nil) + panic("ether1116: nil ctlr->rxb[ctlr->rxhead] " + "in receive"); + ctlr->rxb[ctlr->rxhead] = nil; + ctlr->rxhead = NEXT(ctlr->rxhead, Nrx); + + if((r->cs & (RCSfirst|RCSlast)) != (RCSfirst|RCSlast)) { + ctlr->nofirstlast++; /* partial packet */ + freeb(b); + continue; + } + if(r->cs & RCSmacerr) { + freeb(b); + continue; + } + + n = r->countsize >> 16; /* TODO includes 2 pad bytes? */ + assert(n >= 2 && n < 2048); + + /* clear any cached packet or part thereof */ + l2cacheuinvse(b->rp, n+2); + cachedinvse(b->rp, n+2); + b->wp = b->rp + n; + /* + * skip hardware padding intended to align ipv4 address + * in memory (mv-s104860-u0 §8.3.4.1) + */ + b->rp += 2; + etheriq(ether, b, 1); + etheractive(ether); + if (i % (Nrx / 2) == 0) { + rxreplenish(ctlr); + rxkick(ctlr); + } + } + rxreplenish(ctlr); + rxkick(ctlr); +} + +static void +txreplenish(Ether *ether) /* free transmitted packets */ +{ + Ctlr *ctlr; + + ctlr = ether->ctlr; + while(ctlr->txtail != ctlr->txhead) { + /* ctlr->tx is uncached */ + if(ctlr->tx[ctlr->txtail].cs & TCSdmaown) + break; + if(ctlr->txb[ctlr->txtail] == nil) + panic("no block for sent packet?!"); + freeb(ctlr->txb[ctlr->txtail]); + ctlr->txb[ctlr->txtail] = nil; + + ctlr->txtail = NEXT(ctlr->txtail, Ntx); + etheractive(ether); + } +} + +/* + * transmit strategy: fill the output ring as far as possible, + * perhaps leaving a few spare; kick off the output and take + * an interrupt only when the transmit queue is empty. + */ +static void +transmit(Ether *ether) +{ + int i, kick, len; + Block *b; + Ctlr *ctlr = ether->ctlr; + Gbereg *reg = ctlr->reg; + Tx *t; + + ethercheck(ether); + ilock(ctlr); + txreplenish(ether); /* reap old packets */ + + /* queue new packets; use at most half the tx descs to avoid livelock */ + kick = 0; + for (i = Ntx/2 - 2; i > 0; i--) { + t = &ctlr->tx[ctlr->txhead]; /* *t is uncached */ + assert(((uintptr)t & (Descralign - 1)) == 0); + if(t->cs & TCSdmaown) { /* descriptor busy? */ + ctlr->txringfull++; + break; + } + + b = qget(ether->oq); /* outgoing packet? */ + if (b == nil) + break; + len = BLEN(b); + if(len < ether->minmtu || len > ether->maxmtu) { + freeb(b); + continue; + } + ctlr->txb[ctlr->txhead] = b; + + /* make sure the whole packet is in memory */ + cachedwbse(b->rp, len); + l2cacheuwbse(b->rp, len); + + /* set up the transmit descriptor */ + t->buf = PADDR(b->rp); + t->countchk = len << 16; + coherence(); + + /* and fire */ + t->cs = TCSpadding | TCSfirst | TCSlast | TCSdmaown | + TCSenableintr; + coherence(); + + kick++; + ctlr->txhead = NEXT(ctlr->txhead, Ntx); + } + if (kick) { + txkick(ctlr); + + reg->irqmask |= Itxendq(Qno); + reg->irqemask |= IEtxerrq(Qno) | IEtxunderrun; + } + iunlock(ctlr); +} + +static void +dumprxdescs(Ctlr *ctlr) +{ + int i; + Gbereg *reg = ctlr->reg; + + iprint("\nrxhead %d rxtail %d; txcdp %#p rxcdp %#p\n", + ctlr->rxhead, ctlr->rxtail, reg->tcqdp[Qno], reg->crdp[Qno].r); + for (i = 0; i < Nrx; i++) { + iprint("rxb %d @ %#p: %#p\n", i, &ctlr->rxb[i], ctlr->rxb[i]); + delay(50); + } + for (i = 0; i < Nrx; i++) { + iprint("rx %d @ %#p: cs %#lux countsize %lud buf %#lux next %#lux\n", + i, &ctlr->rx[i], ctlr->rx[i].cs, + ctlr->rx[i].countsize >> 3, ctlr->rx[i].buf, + ctlr->rx[i].next); + delay(50); + } + delay(1000); +} + +static int +gotinput(void* ctlr) +{ + return ((Ctlr*)ctlr)->haveinput != 0; +} + +/* + * process any packets in the input ring. + * also sum mib stats frequently to avoid the overflow + * mentioned in the errata. + */ +static void +rcvproc(void* arg) +{ + Ctlr *ctlr; + Ether *ether; + + ether = arg; + ctlr = ether->ctlr; + for(;;){ + tsleep(&ctlr->rrendez, gotinput, ctlr, 10*1000); + ilock(ctlr); + getmibstats(ctlr); + if (ctlr->haveinput) { + ctlr->haveinput = 0; + iunlock(ctlr); + receive(ether); + } else + iunlock(ctlr); + } +} + +static void +interrupt(Ureg*, void *arg) +{ + ulong irq, irqe, handled; + Ether *ether = arg; + Ctlr *ctlr = ether->ctlr; + Gbereg *reg = ctlr->reg; + + handled = 0; + irq = reg->irq; + irqe = reg->irqe; + reg->irqe = 0; /* extinguish intr causes */ + reg->irq = 0; /* extinguish intr causes */ + ethercheck(ether); + + if(irq & (Irx | Irxbufferq(Qno))) { + /* + * letting a kproc process the input takes far less real time + * than doing it all at interrupt level. + */ + ctlr->haveinput = 1; + wakeup(&ctlr->rrendez); + irq &= ~(Irx | Irxbufferq(Qno)); + handled++; + } else + rxkick(ctlr); + + if(irq & Itxendq(Qno)) { /* transmit ring empty? */ + reg->irqmask &= ~Itxendq(Qno); /* prevent more interrupts */ + reg->irqemask &= ~(IEtxerrq(Qno) | IEtxunderrun); + transmit(ether); + irq &= ~Itxendq(Qno); + handled++; + } + + if(irqe & IEsum) { + /* + * IElinkchg appears to only be set when unplugging. + * autonegotiation is likely not done yet, so linkup not valid, + * thus we note the link change here, and check for + * that and autonegotiation done below. + */ + if(irqe & IEphystschg) { + ether->link = (reg->ps0 & PS0linkup) != 0; + ether->linkchg = 1; + } + if(irqe & IEtxerrq(Qno)) + ether->oerrs++; + if(irqe & IErxoverrun) + ether->overflows++; + if(irqe & IEtxunderrun) + ctlr->txunderrun++; + if(irqe & (IEphystschg | IEtxerrq(Qno) | IErxoverrun | + IEtxunderrun)) + handled++; + } + if (irq & Isum) { + if (irq & Irxerr) { /* nil desc. ptr. or desc. owned by cpu */ + ether->buffs++; /* approx. error */ + + /* if the input ring is full, drain it */ + ctlr->haveinput = 1; + wakeup(&ctlr->rrendez); + } + if(irq & (Irxerr | Irxerrq(Qno))) + handled++; + irq &= ~(Irxerr | Irxerrq(Qno)); + } + + if(ether->linkchg && (reg->ps1 & PS1an_done)) { + handled++; + ether->link = (reg->ps0 & PS0linkup) != 0; + ether->linkchg = 0; + } + ctlr->newintrs++; + + if (!handled) { + irq &= ~Isum; + irqe &= ~IEtxbufferq(Qno); + if (irq == 0 && irqe == 0) { + /* seems to be triggered by continuous output */ + // iprint("ether1116: spurious interrupt\n"); + } else + iprint("ether1116: interrupt cause unknown; " + "irq %#lux irqe %#lux\n", irq, irqe); + } + intrclear(Irqlo, ether->irq); +} + +void +promiscuous(void *arg, int on) +{ + Ether *ether = arg; + Ctlr *ctlr = ether->ctlr; + Gbereg *reg = ctlr->reg; + + ilock(ctlr); + ether->prom = on; + if(on) + reg->portcfg |= PCFGupromisc; + else + reg->portcfg &= ~PCFGupromisc; + iunlock(ctlr); +} + +void +multicast(void *, uchar *, int) +{ + /* nothing to do; we always accept multicast */ +} + +static void quiesce(Gbereg *reg); + +static void +shutdown(Ether *ether) +{ + int i; + Ctlr *ctlr = ether->ctlr; + Gbereg *reg = ctlr->reg; + + ilock(ctlr); + quiesce(reg); + reg->euc |= Portreset; + coherence(); + iunlock(ctlr); + delay(100); + ilock(ctlr); + reg->euc &= ~Portreset; + coherence(); + delay(20); + + reg->psc0 = 0; /* no PSC0porton */ + reg->psc1 |= PSC1portreset; + coherence(); + delay(50); + reg->psc1 &= ~PSC1portreset; + coherence(); + + for (i = 0; i < nelem(reg->tcqdp); i++) + reg->tcqdp[i] = 0; + for (i = 0; i < nelem(reg->crdp); i++) + reg->crdp[i].r = 0; + coherence(); + + iunlock(ctlr); +} + +enum { + CMjumbo, +}; + +static Cmdtab ctlmsg[] = { + CMjumbo, "jumbo", 2, +}; + +long +ctl(Ether *e, void *p, long n) +{ + Cmdbuf *cb; + Cmdtab *ct; + Ctlr *ctlr = e->ctlr; + Gbereg *reg = ctlr->reg; + + cb = parsecmd(p, n); + if(waserror()) { + free(cb); + nexterror(); + } + + ct = lookupcmd(cb, ctlmsg, nelem(ctlmsg)); + switch(ct->index) { + case CMjumbo: + if(strcmp(cb->f[1], "on") == 0) { + /* incoming packet queue doesn't expect jumbo frames */ + error("jumbo disabled"); + reg->psc0 = (reg->psc0 & ~PSC0mrumask) | + PSC0mru(PSC0mru9022); + e->maxmtu = 9022; + } else if(strcmp(cb->f[1], "off") == 0) { + reg->psc0 = (reg->psc0 & ~PSC0mrumask) | + PSC0mru(PSC0mru1522); + e->maxmtu = ETHERMAXTU; + } else + error(Ebadctl); + break; + default: + error(Ebadctl); + break; + } + free(cb); + poperror(); + return n; +} + +/* + * phy/mii goo + */ + +static int +smibusywait(Gbereg *reg, ulong waitbit) +{ + ulong timeout, smi_reg; + + timeout = PhysmiTimeout; + /* wait till the SMI is not busy */ + do { + /* read smi register */ + smi_reg = reg->smi; + if (timeout-- == 0) { + MIIDBG("SMI busy timeout\n"); + return -1; + } +// delay(1); + } while (smi_reg & waitbit); + return 0; +} + +static int +miird(Mii *mii, int pa, int ra) +{ + ulong smi_reg, timeout; + Gbereg *reg; + + reg = ((Ctlr*)mii->ctlr)->reg; + + /* check params */ + if ((pa<<Physmiaddroff) & ~Physmiaddrmask || + (ra<<SmiRegaddroff) & ~SmiRegaddrmask) + return -1; + + smibusywait(reg, PhysmiBusy); + + /* fill the phy address and register offset and read opcode */ + reg->smi = pa << Physmiaddroff | ra << SmiRegaddroff | PhysmiopRd; + coherence(); + + /* wait til read value is ready */ + timeout = PhysmiTimeout; + do { + smi_reg = reg->smi; + if (timeout-- == 0) { + MIIDBG("SMI read-valid timeout\n"); + return -1; + } + } while (!(smi_reg & PhysmiReadok)); + + /* Wait for the data to update in the SMI register */ + for (timeout = 0; timeout < PhysmiTimeout; timeout++) + ; + return reg->smi & Physmidatamask; +} + +static int +miiwr(Mii *mii, int pa, int ra, int v) +{ + Gbereg *reg; + ulong smi_reg; + + reg = ((Ctlr*)mii->ctlr)->reg; + + /* check params */ + if (((pa<<Physmiaddroff) & ~Physmiaddrmask) || + ((ra<<SmiRegaddroff) & ~SmiRegaddrmask)) + return -1; + + smibusywait(reg, PhysmiBusy); + + /* fill the phy address and register offset and read opcode */ + smi_reg = v << Physmidataoff | pa << Physmiaddroff | ra << SmiRegaddroff; + reg->smi = smi_reg & ~PhysmiopRd; + coherence(); + return 0; +} + +#define MIIMODEL(idr2) (((idr2) >> 4) & MASK(6)) + +enum { + Hacknone, + Hackdual, + + Ouimarvell = 0x005043, + + /* idr2 mii/phy model numbers */ + Phy1000 = 0x00, /* 88E1000 Gb */ + Phy1011 = 0x02, /* 88E1011 Gb */ + Phy1000_3 = 0x03, /* 88E1000 Gb */ + Phy1000s = 0x04, /* 88E1000S Gb */ + Phy1000_5 = 0x05, /* 88E1000 Gb */ + Phy1000_6 = 0x06, /* 88E1000 Gb */ + Phy3082 = 0x08, /* 88E3082 10/100 */ + Phy1112 = 0x09, /* 88E1112 Gb */ + Phy1121r = 0x0b, /* says the 1121r manual */ + Phy1149 = 0x0b, /* 88E1149 Gb */ + Phy1111 = 0x0c, /* 88E1111 Gb */ + Phy1116 = 0x21, /* 88E1116 Gb */ + Phy1116r = 0x24, /* 88E1116R Gb */ + Phy1118 = 0x22, /* 88E1118 Gb */ + Phy3016 = 0x26, /* 88E3016 10/100 */ +}; + +static int hackflavour; + +/* + * on openrd, ether0's phy has address 8, ether1's is ether0's 24. + * on guruplug, ether0's is phy 0 and ether1's is ether0's phy 1. + */ +int +mymii(Mii* mii, int mask) +{ + Ctlr *ctlr; + MiiPhy *miiphy; + int bit, ctlrno, oui, model, phyno, r, rmask; + static int dualport, phyidx; + static int phynos[NMiiPhy]; + + ctlr = mii->ctlr; + ctlrno = ctlr->ether->ctlrno; + + /* first pass: figure out what kind of phy(s) we have. */ + dualport = 0; + if (ctlrno == 0) { + for(phyno = 0; phyno < NMiiPhy; phyno++){ + bit = 1<<phyno; + if(!(mask & bit) || mii->mask & bit) + continue; + if(mii->mir(mii, phyno, Bmsr) == -1) + continue; + r = mii->mir(mii, phyno, Phyidr1); + oui = (r & 0x3FFF)<<6; + r = mii->mir(mii, phyno, Phyidr2); + oui |= r>>10; + model = MIIMODEL(r); + if (oui == 0xfffff && model == 0x3f) + continue; + MIIDBG("ctlrno %d phy %d oui %#ux model %#ux\n", + ctlrno, phyno, oui, model); + if (oui == Ouimarvell && + (model == Phy1121r || model == Phy1116r)) + ++dualport; + phynos[phyidx++] = phyno; + } + hackflavour = dualport == 2 && phyidx == 2? Hackdual: Hacknone; + MIIDBG("ether1116: %s-port phy\n", + hackflavour == Hackdual? "dual": "single"); + } + + /* + * Probe through mii for PHYs in mask; + * return the mask of those found in the current probe. + * If the PHY has not already been probed, update + * the Mii information. + */ + rmask = 0; + if (hackflavour == Hackdual && ctlrno < phyidx) { + /* + * openrd, guruplug or the like: use ether0's phys. + * this is a nasty hack, but so is the hardware. + */ + MIIDBG("ctlrno %d using ctlrno 0's phyno %d\n", + ctlrno, phynos[ctlrno]); + ctlr->mii = mii = ctlrs[0]->mii; + mask = 1 << phynos[ctlrno]; + mii->mask = ~mask; + } + for(phyno = 0; phyno < NMiiPhy; phyno++){ + bit = 1<<phyno; + if(!(mask & bit)) + continue; + if(mii->mask & bit){ + rmask |= bit; + continue; + } + if(mii->mir(mii, phyno, Bmsr) == -1) + continue; + r = mii->mir(mii, phyno, Phyidr1); + oui = (r & 0x3FFF)<<6; + r = mii->mir(mii, phyno, Phyidr2); + oui |= r>>10; + if(oui == 0xFFFFF || oui == 0) + continue; + + if((miiphy = malloc(sizeof(MiiPhy))) == nil) + continue; + miiphy->mii = mii; + miiphy->oui = oui; + miiphy->phyno = phyno; + + miiphy->anar = ~0; + miiphy->fc = ~0; + miiphy->mscr = ~0; + + mii->phy[phyno] = miiphy; + if(ctlrno == 0 || hackflavour != Hackdual && mii->curphy == nil) + mii->curphy = miiphy; + mii->mask |= bit; + mii->nphy++; + + rmask |= bit; + } + return rmask; +} + +static int +kirkwoodmii(Ether *ether) +{ + int i; + Ctlr *ctlr; + MiiPhy *phy; + + MIIDBG("mii\n"); + ctlr = ether->ctlr; + if((ctlr->mii = malloc(sizeof(Mii))) == nil) + return -1; + ctlr->mii->ctlr = ctlr; + ctlr->mii->mir = miird; + ctlr->mii->miw = miiwr; + + if(mymii(ctlr->mii, ~0) == 0 || (phy = ctlr->mii->curphy) == nil){ + print("#l%d: ether1116: init mii failure\n", ether->ctlrno); + free(ctlr->mii); + ctlr->mii = nil; + return -1; + } + + /* oui 005043 is marvell */ + MIIDBG("oui %#X phyno %d\n", phy->oui, phy->phyno); + // TODO: does this make sense? shouldn't each phy be initialised? + if((ctlr->ether->ctlrno == 0 || hackflavour != Hackdual) && + miistatus(ctlr->mii) < 0){ + miireset(ctlr->mii); + MIIDBG("miireset\n"); + if(miiane(ctlr->mii, ~0, 0, ~0) < 0){ + iprint("miiane failed\n"); + return -1; + } + MIIDBG("miistatus\n"); + miistatus(ctlr->mii); + if(miird(ctlr->mii, phy->phyno, Bmsr) & BmsrLs){ + for(i = 0; ; i++){ + if(i > 600){ + iprint("ether1116: autonegotiation failed\n"); + break; + } + if(miird(ctlr->mii, phy->phyno, Bmsr) & BmsrAnc) + break; + delay(10); + } + if(miistatus(ctlr->mii) < 0) + iprint("miistatus failed\n"); + }else{ + iprint("ether1116: no link\n"); + phy->speed = 10; /* simple default */ + } + } + + ether->mbps = phy->speed; + MIIDBG("#l%d: kirkwoodmii: fd %d speed %d tfc %d rfc %d\n", + ctlr->port, phy->fd, phy->speed, phy->tfc, phy->rfc); + MIIDBG("mii done\n"); + return 0; +} + +enum { /* PHY register pages */ + Pagcopper, + Pagfiber, + Pagrgmii, + Pagled, + Pagrsvd1, + Pagvct, + Pagtest, + Pagrsvd2, + Pagfactest, +}; + +static void +miiregpage(Mii *mii, ulong dev, ulong page) +{ + miiwr(mii, dev, Eadr, page); +} + +static int +miiphyinit(Mii *mii) +{ + ulong dev; + Ctlr *ctlr; + Gbereg *reg; + + ctlr = (Ctlr*)mii->ctlr; + reg = ctlr->reg; + dev = reg->phy; + MIIDBG("phy dev addr %lux\n", dev); + + /* leds link & activity */ + miiregpage(mii, dev, Pagled); + /* low 4 bits == 1: on - link, blink - activity, off - no link */ + miiwr(mii, dev, Scr, (miird(mii, dev, Scr) & ~0xf) | 1); + + miiregpage(mii, dev, Pagrgmii); + miiwr(mii, dev, Scr, miird(mii, dev, Scr) | Rgmiipwrup); + /* must now do a software reset, says the manual */ + miireset(ctlr->mii); + + /* enable RGMII delay on Tx and Rx for CPU port */ + miiwr(mii, dev, Recr, miird(mii, dev, Recr) | Rxtiming | Rxtiming); + /* must now do a software reset, says the manual */ + miireset(ctlr->mii); + + miiregpage(mii, dev, Pagcopper); + miiwr(mii, dev, Scr, + (miird(mii, dev, Scr) & ~(Pwrdown|Endetect)) | Mdix); + + return 0; +} + +/* + * initialisation + */ + +static void +quiesce(Gbereg *reg) +{ + ulong v; + + v = reg->tqc; + if (v & 0xFF) + reg->tqc = v << 8; /* stop active channels */ + v = reg->rqc; + if (v & 0xFF) + reg->rqc = v << 8; /* stop active channels */ + /* wait for all queues to stop */ + while (reg->tqc & 0xFF || reg->rqc & 0xFF) + ; +} + +static void +p16(uchar *p, ulong v) /* convert big-endian short to bytes */ +{ + *p++ = v>>8; + *p = v; +} + +static void +p32(uchar *p, ulong v) /* convert big-endian long to bytes */ +{ + *p++ = v>>24; + *p++ = v>>16; + *p++ = v>>8; + *p = v; +} + +/* + * set ether->ea from hw mac address, + * configure unicast filtering to accept it. + */ +void +archetheraddr(Ether *ether, Gbereg *reg, int rxqno) +{ + uchar *ea; + ulong nibble, ucreg, tbloff, regoff; + + ea = ether->ea; + p32(ea, reg->macah); + p16(ea+4, reg->macal); + if (memcmp(ea, zeroea, sizeof zeroea) == 0 && ether->ctlrno > 0) { + /* hack: use ctlr[0]'s + ctlrno */ + memmove(ea, ctlrs[0]->ether->ea, Eaddrlen); + ea[Eaddrlen-1] += ether->ctlrno; + reg->macah = ea[0] << 24 | ea[1] << 16 | ea[2] << 8 | ea[3]; + reg->macal = ea[4] << 8 | ea[5]; + coherence(); + } + + /* accept frames on ea */ + nibble = ea[5] & 0xf; + tbloff = nibble / 4; + regoff = nibble % 4; + + regoff *= 8; + ucreg = reg->dfut[tbloff] & (0xff << regoff); + ucreg |= (rxqno << 1 | Pass) << regoff; + reg->dfut[tbloff] = ucreg; + + /* accept all multicast too. set up special & other tables. */ + memset(reg->dfsmt, Qno<<1 | Pass, sizeof reg->dfsmt); + memset(reg->dfomt, Qno<<1 | Pass, sizeof reg->dfomt); + coherence(); +} + +static void +cfgdramacc(Gbereg *reg) +{ + memset(reg->harr, 0, sizeof reg->harr); + memset(reg->base, 0, sizeof reg->base); + + reg->bare = MASK(6) - MASK(2); /* disable wins 2-5 */ + /* this doesn't make any sense, but it's required */ + reg->epap = 3 << 2 | 3; /* full access for wins 0 & 1 */ +// reg->epap = 0; /* no access on access violation for all wins */ + coherence(); + + reg->base[0].base = PHYSDRAM | WINATTR(Attrcs0) | Targdram; + reg->base[0].size = WINSIZE(256*MB); + reg->base[1].base = (PHYSDRAM + 256*MB) | WINATTR(Attrcs1) | Targdram; + reg->base[1].size = WINSIZE(256*MB); + coherence(); +} + +static void +ctlralloc(Ctlr *ctlr) +{ + int i; + Block *b; + Rx *r; + Tx *t; + + ilock(&freeblocks); + for(i = 0; i < Nrxblks; i++) { + b = iallocb(Rxblklen+Bufalign-1); + if(b == nil) { + iprint("ether1116: no memory for rx buffers\n"); + break; + } + assert(b->ref == 1); + b->wp = b->rp = (uchar*) + ((uintptr)(b->lim - Rxblklen) & ~(Bufalign - 1)); + assert(((uintptr)b->rp & (Bufalign - 1)) == 0); + b->free = rxfreeb; + b->next = freeblocks.head; + freeblocks.head = b; + } + iunlock(&freeblocks); + + /* + * allocate uncached rx ring descriptors because rings are shared + * with the ethernet controller and more than one fits in a cache line. + */ + ctlr->rx = ucallocalign(Nrx * sizeof(Rx), Descralign, 0); + if(ctlr->rx == nil) + panic("ether1116: no memory for rx ring"); + for(i = 0; i < Nrx; i++) { + r = &ctlr->rx[i]; + assert(((uintptr)r & (Descralign - 1)) == 0); + r->cs = 0; /* owned by software until r->buf is non-nil */ + r->buf = 0; + r->next = PADDR(&ctlr->rx[NEXT(i, Nrx)]); + ctlr->rxb[i] = nil; + } + ctlr->rxtail = ctlr->rxhead = 0; + rxreplenish(ctlr); + + /* allocate uncached tx ring descriptors */ + ctlr->tx = ucallocalign(Ntx * sizeof(Tx), Descralign, 0); + if(ctlr->tx == nil) + panic("ether1116: no memory for tx ring"); + for(i = 0; i < Ntx; i++) { + t = &ctlr->tx[i]; + assert(((uintptr)t & (Descralign - 1)) == 0); + t->cs = 0; + t->buf = 0; + t->next = PADDR(&ctlr->tx[NEXT(i, Ntx)]); + ctlr->txb[i] = nil; + } + ctlr->txtail = ctlr->txhead = 0; +} + +static void +ctlrinit(Ether *ether) +{ + int i; + Ctlr *ctlr = ether->ctlr; + Gbereg *reg = ctlr->reg; + static char name[KNAMELEN]; + static Ctlr fakectlr; /* bigger than 4K; keep off the stack */ + + for (i = 0; i < nelem(reg->tcqdp); i++) + reg->tcqdp[i] = 0; + for (i = 0; i < nelem(reg->crdp); i++) + reg->crdp[i].r = 0; + coherence(); + + cfgdramacc(reg); + ctlralloc(ctlr); + + reg->tcqdp[Qno] = PADDR(&ctlr->tx[ctlr->txhead]); + reg->crdp[Qno].r = PADDR(&ctlr->rx[ctlr->rxhead]); + coherence(); + +// dumprxdescs(ctlr); + + /* clear stats by reading them into fake ctlr */ + getmibstats(&fakectlr); + + reg->pxmfs = MFS40by; /* allow runts in */ + + /* + * ipg's (inter packet gaps) for interrupt coalescing, + * values in units of 64 clock cycles. A full-sized + * packet (1514 bytes) takes just over 12µs to transmit. + */ + if (CLOCKFREQ/(Maxrxintrsec*64) >= (1<<16)) + panic("rx coalescing value %d too big for short", + CLOCKFREQ/(Maxrxintrsec*64)); + reg->sdc = SDCrifb | SDCrxburst(Burst16) | SDCtxburst(Burst16) | + SDCrxnobyteswap | SDCtxnobyteswap | + SDCipgintrx(CLOCKFREQ/(Maxrxintrsec*64)); + reg->pxtfut = 0; /* TFUTipginttx(CLOCKFREQ/(Maxrxintrsec*64)) */ + + /* allow just these interrupts */ + /* guruplug generates Irxerr interrupts continually */ + reg->irqmask = Isum | Irx | Irxbufferq(Qno) | Irxerr | Itxendq(Qno); + reg->irqemask = IEsum | IEtxerrq(Qno) | IEphystschg | IErxoverrun | + IEtxunderrun; + + reg->irqe = 0; + reg->euirqmask = 0; + coherence(); + reg->irq = 0; + reg->euirq = 0; + /* send errors to end of memory */ +// reg->euda = PHYSDRAM + 512*MB - 8*1024; + reg->euda = 0; + reg->eudid = Attrcs1 << 4 | Targdram; + +// archetheraddr(ether, ctlr->reg, Qno); /* 2nd location */ + + reg->portcfg = Rxqdefault(Qno) | Rxqarp(Qno); + reg->portcfgx = 0; + coherence(); + + /* + * start the controller running. + * turn the port on, kick the receiver. + */ + + reg->psc1 = PSC1rgmii | PSC1encolonbp | PSC1coldomlim(0x23); + /* do this only when the controller is quiescent */ + reg->psc0 = PSC0porton | PSC0an_flctloff | + PSC0an_pauseadv | PSC0nofrclinkdown | PSC0mru(PSC0mru1522); + coherence(); + for (i = 0; i < 4000; i++) /* magic delay */ + ; + + ether->link = (reg->ps0 & PS0linkup) != 0; + + /* set ethernet MTU for leaky bucket mechanism to 0 (disabled) */ + reg->pmtu = 0; + etheractive(ether); + + snprint(name, sizeof name, "#l%drproc", ether->ctlrno); + kproc(name, rcvproc, ether); + + reg->rqc = Rxqon(Qno); + coherence(); +} + +static void +attach(Ether* ether) +{ + Ctlr *ctlr = ether->ctlr; + + lock(&ctlr->initlock); + if(ctlr->init == 0) { + ctlrinit(ether); + ctlr->init = 1; + } + unlock(&ctlr->initlock); +} + +/* + * statistics goo. + * mib registers clear on read. + */ + +static void +getmibstats(Ctlr *ctlr) +{ + Gbereg *reg = ctlr->reg; + + /* + * Marvell 88f6281 errata FE-ETH-120: high long of rxby and txby + * can't be read correctly, so read the low long frequently + * (every 30 seconds or less), thus avoiding overflow into high long. + */ + ctlr->rxby += reg->rxbylo; + ctlr->txby += reg->txbylo; + + ctlr->badrxby += reg->badrxby; + ctlr->mactxerr += reg->mactxerr; + ctlr->rxpkt += reg->rxpkt; + ctlr->badrxpkt += reg->badrxpkt; + ctlr->rxbcastpkt+= reg->rxbcastpkt; + ctlr->rxmcastpkt+= reg->rxmcastpkt; + ctlr->rx64 += reg->rx64; + ctlr->rx65_127 += reg->rx65_127; + ctlr->rx128_255 += reg->rx128_255; + ctlr->rx256_511 += reg->rx256_511; + ctlr->rx512_1023+= reg->rx512_1023; + ctlr->rx1024_max+= reg->rx1024_max; + ctlr->txpkt += reg->txpkt; + ctlr->txcollpktdrop+= reg->txcollpktdrop; + ctlr->txmcastpkt+= reg->txmcastpkt; + ctlr->txbcastpkt+= reg->txbcastpkt; + ctlr->badmacctlpkts+= reg->badmacctlpkts; + ctlr->txflctl += reg->txflctl; + ctlr->rxflctl += reg->rxflctl; + ctlr->badrxflctl+= reg->badrxflctl; + ctlr->rxundersized+= reg->rxundersized; + ctlr->rxfrags += reg->rxfrags; + ctlr->rxtoobig += reg->rxtoobig; + ctlr->rxjabber += reg->rxjabber; + ctlr->rxerr += reg->rxerr; + ctlr->crcerr += reg->crcerr; + ctlr->collisions+= reg->collisions; + ctlr->latecoll += reg->latecoll; +} + +long +ifstat(Ether *ether, void *a, long n, ulong off) +{ + Ctlr *ctlr = ether->ctlr; + Gbereg *reg = ctlr->reg; + char *buf, *p, *e; + + buf = p = malloc(READSTR); + e = p + READSTR; + + ilock(ctlr); + getmibstats(ctlr); + + ctlr->intrs += ctlr->newintrs; + p = seprint(p, e, "interrupts: %lud\n", ctlr->intrs); + p = seprint(p, e, "new interrupts: %lud\n", ctlr->newintrs); + ctlr->newintrs = 0; + p = seprint(p, e, "tx underrun: %lud\n", ctlr->txunderrun); + p = seprint(p, e, "tx ring full: %lud\n", ctlr->txringfull); + + ctlr->rxdiscard += reg->pxdfc; + ctlr->rxoverrun += reg->pxofc; + p = seprint(p, e, "rx discarded frames: %lud\n", ctlr->rxdiscard); + p = seprint(p, e, "rx overrun frames: %lud\n", ctlr->rxoverrun); + p = seprint(p, e, "no first+last flag: %lud\n", ctlr->nofirstlast); + + p = seprint(p, e, "duplex: %s\n", (reg->ps0 & PS0fd)? "full": "half"); + p = seprint(p, e, "flow control: %s\n", (reg->ps0 & PS0flctl)? "on": "off"); + /* p = seprint(p, e, "speed: %d mbps\n", ); */ + + p = seprint(p, e, "received bytes: %llud\n", ctlr->rxby); + p = seprint(p, e, "bad received bytes: %lud\n", ctlr->badrxby); + p = seprint(p, e, "internal mac transmit errors: %lud\n", ctlr->mactxerr); + p = seprint(p, e, "total received frames: %lud\n", ctlr->rxpkt); + p = seprint(p, e, "received broadcast frames: %lud\n", ctlr->rxbcastpkt); + p = seprint(p, e, "received multicast frames: %lud\n", ctlr->rxmcastpkt); + p = seprint(p, e, "bad received frames: %lud\n", ctlr->badrxpkt); + p = seprint(p, e, "received frames 0-64: %lud\n", ctlr->rx64); + p = seprint(p, e, "received frames 65-127: %lud\n", ctlr->rx65_127); + p = seprint(p, e, "received frames 128-255: %lud\n", ctlr->rx128_255); + p = seprint(p, e, "received frames 256-511: %lud\n", ctlr->rx256_511); + p = seprint(p, e, "received frames 512-1023: %lud\n", ctlr->rx512_1023); + p = seprint(p, e, "received frames 1024-max: %lud\n", ctlr->rx1024_max); + p = seprint(p, e, "transmitted bytes: %llud\n", ctlr->txby); + p = seprint(p, e, "total transmitted frames: %lud\n", ctlr->txpkt); + p = seprint(p, e, "transmitted broadcast frames: %lud\n", ctlr->txbcastpkt); + p = seprint(p, e, "transmitted multicast frames: %lud\n", ctlr->txmcastpkt); + p = seprint(p, e, "transmit frames dropped by collision: %lud\n", ctlr->txcollpktdrop); + p = seprint(p, e, "misaligned buffers: %lud\n", ether->pktsmisaligned); + + p = seprint(p, e, "bad mac control frames: %lud\n", ctlr->badmacctlpkts); + p = seprint(p, e, "transmitted flow control messages: %lud\n", ctlr->txflctl); + p = seprint(p, e, "received flow control messages: %lud\n", ctlr->rxflctl); + p = seprint(p, e, "bad received flow control messages: %lud\n", ctlr->badrxflctl); + p = seprint(p, e, "received undersized packets: %lud\n", ctlr->rxundersized); + p = seprint(p, e, "received fragments: %lud\n", ctlr->rxfrags); + p = seprint(p, e, "received oversized packets: %lud\n", ctlr->rxtoobig); + p = seprint(p, e, "received jabber packets: %lud\n", ctlr->rxjabber); + p = seprint(p, e, "mac receive errors: %lud\n", ctlr->rxerr); + p = seprint(p, e, "crc errors: %lud\n", ctlr->crcerr); + p = seprint(p, e, "collisions: %lud\n", ctlr->collisions); + p = seprint(p, e, "late collisions: %lud\n", ctlr->latecoll); + USED(p); + iunlock(ctlr); + + n = readstr(off, a, n, buf); + free(buf); + return n; +} + + +static int +reset(Ether *ether) +{ + Ctlr *ctlr; + + ether->ctlr = ctlr = malloc(sizeof *ctlr); + switch(ether->ctlrno) { + case 0: + ether->irq = IRQ0gbe0sum; + break; + case 1: + ether->irq = IRQ0gbe1sum; + break; + default: + panic("ether1116: bad ether ctlr #%d", ether->ctlrno); + } + ctlr->reg = (Gbereg*)soc.ether[ether->ctlrno]; + + /* need this for guruplug, at least */ + *(ulong *)soc.iocfg |= 1 << 7 | 1 << 15; /* io cfg 0: 1.8v gbe */ + coherence(); + + ctlr->ether = ether; + ctlrs[ether->ctlrno] = ctlr; + + shutdown(ether); + /* ensure that both interfaces are set to RGMII before calling mii */ + ((Gbereg*)soc.ether[0])->psc1 |= PSC1rgmii; + ((Gbereg*)soc.ether[1])->psc1 |= PSC1rgmii; + coherence(); + + /* Set phy address of the port */ + ctlr->port = ether->ctlrno; + ctlr->reg->phy = ether->ctlrno; + coherence(); + ether->port = (uintptr)ctlr->reg; + + if(kirkwoodmii(ether) < 0){ + free(ctlr); + ether->ctlr = nil; + return -1; + } + miiphyinit(ctlr->mii); + archetheraddr(ether, ctlr->reg, Qno); /* original location */ + if (memcmp(ether->ea, zeroea, sizeof zeroea) == 0){ +iprint("ether1116: reset: zero ether->ea\n"); + free(ctlr); + ether->ctlr = nil; + return -1; /* no rj45 for this ether */ + } + + ether->attach = attach; + ether->transmit = transmit; + ether->interrupt = interrupt; + ether->ifstat = ifstat; + ether->shutdown = shutdown; + ether->ctl = ctl; + + ether->arg = ether; + ether->promiscuous = promiscuous; + ether->multicast = multicast; + return 0; +} + +void +ether1116link(void) +{ + addethercard("88e1116", reset); +} diff --git a/sys/src/9/kw/etherif.h b/sys/src/9/kw/etherif.h new file mode 100755 index 000000000..35d131fe3 --- /dev/null +++ b/sys/src/9/kw/etherif.h @@ -0,0 +1,55 @@ +enum +{ + MaxEther = 2, + Ntypes = 8, +}; + +typedef struct Ether Ether; +struct Ether { + RWlock; /* TO DO */ + ISAConf; /* hardware info */ + int ctlrno; + int minmtu; + int maxmtu; + uchar ea[Eaddrlen]; + void *address; + int tbusy; + + void (*attach)(Ether*); /* filled in by reset routine */ + void (*closed)(Ether*); + 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; + int pcmslot; /* PCMCIA */ + int fullduplex; /* non-zero if full duplex */ + int linkchg; /* link status changed? */ + uvlong starttime; /* last activity time */ + + Queue* oq; + + /* statistics */ + ulong interrupts; + ulong dmarxintr; + ulong dmatxintr; + ulong promisc; + ulong pktsdropped; + ulong pktsmisaligned; + ulong resets; /* after initialisation */ + ulong bcasts; /* broadcast pkts rcv'd */ + ulong mcasts; /* multicast pkts rcv'd */ + + Netif; +}; + +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) diff --git a/sys/src/9/kw/ethermii.c b/sys/src/9/kw/ethermii.c new file mode 100755 index 000000000..90b219b3f --- /dev/null +++ b/sys/src/9/kw/ethermii.c @@ -0,0 +1,235 @@ +#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" +#include "ethermii.h" + +int +mii(Mii* mii, int mask) +{ + MiiPhy *miiphy; + int bit, oui, phyno, r, rmask; + + /* + * Probe through mii for PHYs in mask; + * return the mask of those found in the current probe. + * If the PHY has not already been probed, update + * the Mii information. + */ + rmask = 0; + for(phyno = 0; phyno < NMiiPhy; phyno++){ + bit = 1<<phyno; + if(!(mask & bit)) + continue; + if(mii->mask & bit){ + rmask |= bit; + continue; + } + if(mii->mir(mii, phyno, Bmsr) == -1) + continue; + r = mii->mir(mii, phyno, Phyidr1); + oui = (r & 0x3FFF)<<6; + r = mii->mir(mii, phyno, Phyidr2); + oui |= r>>10; + if(oui == 0xFFFFF || oui == 0) + continue; + + if((miiphy = malloc(sizeof(MiiPhy))) == nil) + continue; + + miiphy->mii = mii; + miiphy->oui = oui; + miiphy->phyno = phyno; + + miiphy->anar = ~0; + miiphy->fc = ~0; + miiphy->mscr = ~0; + + mii->phy[phyno] = miiphy; + if(mii->curphy == nil) + mii->curphy = miiphy; + mii->mask |= bit; + mii->nphy++; + + rmask |= bit; + } + return rmask; +} + +int +miimir(Mii* mii, int r) +{ + if(mii == nil || mii->ctlr == nil || mii->curphy == nil) + return -1; + return mii->mir(mii, mii->curphy->phyno, r); +} + +int +miimiw(Mii* mii, int r, int data) +{ + if(mii == nil || mii->ctlr == nil || mii->curphy == nil) + return -1; + return mii->miw(mii, mii->curphy->phyno, r, data); +} + +int +miireset(Mii* mii) +{ + int bmcr; + + if(mii == nil || mii->ctlr == nil || mii->curphy == nil) + return -1; + bmcr = mii->mir(mii, mii->curphy->phyno, Bmcr); + bmcr |= BmcrR; + mii->miw(mii, mii->curphy->phyno, Bmcr, bmcr); + microdelay(1); + + return 0; +} + +int +miiane(Mii* mii, int a, int p, int e) +{ + int anar, bmsr, mscr, r, phyno; + + if(mii == nil || mii->ctlr == nil || mii->curphy == nil) + return -1; + phyno = mii->curphy->phyno; + + bmsr = mii->mir(mii, phyno, Bmsr); + if(!(bmsr & BmsrAna)) + return -1; + + if(a != ~0) + anar = (AnaTXFD|AnaTXHD|Ana10FD|Ana10HD) & a; + else if(mii->curphy->anar != ~0) + anar = mii->curphy->anar; + else{ + anar = mii->mir(mii, phyno, Anar); + anar &= ~(AnaAP|AnaP|AnaT4|AnaTXFD|AnaTXHD|Ana10FD|Ana10HD); + if(bmsr & Bmsr10THD) + anar |= Ana10HD; + if(bmsr & Bmsr10TFD) + anar |= Ana10FD; + if(bmsr & Bmsr100TXHD) + anar |= AnaTXHD; + if(bmsr & Bmsr100TXFD) + anar |= AnaTXFD; + } + mii->curphy->anar = anar; + + if(p != ~0) + anar |= (AnaAP|AnaP) & p; + else if(mii->curphy->fc != ~0) + anar |= mii->curphy->fc; + mii->curphy->fc = (AnaAP|AnaP) & anar; + + if(bmsr & BmsrEs){ + mscr = mii->mir(mii, phyno, Mscr); + mscr &= ~(Mscr1000TFD|Mscr1000THD); + if(e != ~0) + mscr |= (Mscr1000TFD|Mscr1000THD) & e; + else if(mii->curphy->mscr != ~0) + mscr = mii->curphy->mscr; + else{ + r = mii->mir(mii, phyno, Esr); + if(r & Esr1000THD) + mscr |= Mscr1000THD; + if(r & Esr1000TFD) + mscr |= Mscr1000TFD; + } + mii->curphy->mscr = mscr; + mii->miw(mii, phyno, Mscr, mscr); + } + mii->miw(mii, phyno, Anar, anar); + + r = mii->mir(mii, phyno, Bmcr); + if(!(r & BmcrR)){ + r |= BmcrAne|BmcrRan; + mii->miw(mii, phyno, Bmcr, r); + } + + return 0; +} + +int +miistatus(Mii* mii) +{ + MiiPhy *phy; + int anlpar, bmsr, p, r, phyno; + + if(mii == nil || mii->ctlr == nil || mii->curphy == nil) + return -1; + phy = mii->curphy; + phyno = phy->phyno; + + /* + * Check Auto-Negotiation is complete and link is up. + * (Read status twice as the Ls bit is sticky). + */ + bmsr = mii->mir(mii, phyno, Bmsr); + if(!(bmsr & (BmsrAnc|BmsrAna))) { + // print("miistatus: auto-neg incomplete\n"); + return -1; + } + + bmsr = mii->mir(mii, phyno, Bmsr); + if(!(bmsr & BmsrLs)){ + // print("miistatus: link down\n"); + phy->link = 0; + return -1; + } + + phy->speed = phy->fd = phy->rfc = phy->tfc = 0; + if(phy->mscr){ + r = mii->mir(mii, phyno, Mssr); + if((phy->mscr & Mscr1000TFD) && (r & Mssr1000TFD)){ + phy->speed = 1000; + phy->fd = 1; + } + else if((phy->mscr & Mscr1000THD) && (r & Mssr1000THD)) + phy->speed = 1000; + } + + anlpar = mii->mir(mii, phyno, Anlpar); + if(phy->speed == 0){ + r = phy->anar & anlpar; + if(r & AnaTXFD){ + phy->speed = 100; + phy->fd = 1; + } + else if(r & AnaTXHD) + phy->speed = 100; + else if(r & Ana10FD){ + phy->speed = 10; + phy->fd = 1; + } + else if(r & Ana10HD) + phy->speed = 10; + } + if(phy->speed == 0) { + // print("miistatus: phy speed 0\n"); + return -1; + } + + if(phy->fd){ + p = phy->fc; + r = anlpar & (AnaAP|AnaP); + if(p == AnaAP && r == (AnaAP|AnaP)) + phy->tfc = 1; + else if(p == (AnaAP|AnaP) && r == AnaAP) + phy->rfc = 1; + else if((p & AnaP) && (r & AnaP)) + phy->rfc = phy->tfc = 1; + } + + phy->link = 1; + + return 0; +} diff --git a/sys/src/9/kw/ethermii.h b/sys/src/9/kw/ethermii.h new file mode 100755 index 000000000..524a5743d --- /dev/null +++ b/sys/src/9/kw/ethermii.h @@ -0,0 +1,143 @@ +typedef struct Mii Mii; +typedef struct MiiPhy MiiPhy; + +enum { /* registers */ + Bmcr = 0, /* Basic Mode Control */ + Bmsr = 1, /* Basic Mode Status */ + Phyidr1 = 2, /* PHY Identifier #1 */ + Phyidr2 = 3, /* PHY Identifier #2 */ + Anar = 4, /* Auto-Negotiation Advertisement */ + Anlpar = 5, /* AN Link Partner Ability */ + Aner = 6, /* AN Expansion */ + Annptr = 7, /* AN Next Page TX */ + Annprr = 8, /* AN Next Page RX */ + Mscr = 9, /* Gb Control */ + Mssr = 10, /* Gb Status */ + Esr = 15, /* Extended Status */ + + /* 88e1116-specific paged registers */ + Scr = 16, /* special control register */ + Ssr = 17, /* special status register */ + Ier = 18, /* interrupt enable reg */ + Isr = 19, /* interrupt status reg */ + Escr = 20, /* extended special control reg */ + Recr = 21, /* RX error counter reg */ + Eadr = 22, /* extended address reg (page select) */ + Globsts = 23, /* global status */ + Impover = 24, /* RGMII output impedance override (page 2) */ + Imptarg = 25, /* RGMII output impedance target (page 2) */ + Scr2 = 26, /* special control register 2 */ + + NMiiPhyr = 32, + NMiiPhy = 32, +}; + +enum { /* Bmcr */ + BmcrSs1 = 0x0040, /* Speed Select[1] */ + BmcrCte = 0x0080, /* Collision Test Enable */ + BmcrDm = 0x0100, /* Duplex Mode */ + BmcrRan = 0x0200, /* Restart Auto-Negotiation */ + BmcrI = 0x0400, /* Isolate */ + BmcrPd = 0x0800, /* Power Down */ + BmcrAne = 0x1000, /* Auto-Negotiation Enable */ + BmcrSs0 = 0x2000, /* Speed Select[0] */ + BmcrLe = 0x4000, /* Loopback Enable */ + BmcrR = 0x8000, /* Reset */ +}; + +enum { /* Bmsr */ + BmsrEc = 0x0001, /* Extended Capability */ + BmsrJd = 0x0002, /* Jabber Detect */ + BmsrLs = 0x0004, /* Link Status */ + BmsrAna = 0x0008, /* Auto-Negotiation Ability */ + BmsrRf = 0x0010, /* Remote Fault */ + BmsrAnc = 0x0020, /* Auto-Negotiation Complete */ + BmsrPs = 0x0040, /* Preamble Suppression Capable */ + BmsrEs = 0x0100, /* Extended Status */ + Bmsr100T2HD = 0x0200, /* 100BASE-T2 HD Capable */ + Bmsr100T2FD = 0x0400, /* 100BASE-T2 FD Capable */ + Bmsr10THD = 0x0800, /* 10BASE-T HD Capable */ + Bmsr10TFD = 0x1000, /* 10BASE-T FD Capable */ + Bmsr100TXHD = 0x2000, /* 100BASE-TX HD Capable */ + Bmsr100TXFD = 0x4000, /* 100BASE-TX FD Capable */ + Bmsr100T4 = 0x8000, /* 100BASE-T4 Capable */ +}; + +enum { /* Anar/Anlpar */ + Ana10HD = 0x0020, /* Advertise 10BASE-T */ + Ana10FD = 0x0040, /* Advertise 10BASE-T FD */ + AnaTXHD = 0x0080, /* Advertise 100BASE-TX */ + AnaTXFD = 0x0100, /* Advertise 100BASE-TX FD */ + AnaT4 = 0x0200, /* Advertise 100BASE-T4 */ + AnaP = 0x0400, /* Pause */ + AnaAP = 0x0800, /* Asymmetrical Pause */ + AnaRf = 0x2000, /* Remote Fault */ + AnaAck = 0x4000, /* Acknowledge */ + AnaNp = 0x8000, /* Next Page Indication */ +}; + +enum { /* Mscr */ + Mscr1000THD = 0x0100, /* Advertise 1000BASE-T HD */ + Mscr1000TFD = 0x0200, /* Advertise 1000BASE-T FD */ +}; + +enum { /* Mssr */ + Mssr1000THD = 0x0400, /* Link Partner 1000BASE-T HD able */ + Mssr1000TFD = 0x0800, /* Link Partner 1000BASE-T FD able */ +}; + +enum { /* Esr */ + Esr1000THD = 0x1000, /* 1000BASE-T HD Capable */ + Esr1000TFD = 0x2000, /* 1000BASE-T FD Capable */ + Esr1000XHD = 0x4000, /* 1000BASE-X HD Capable */ + Esr1000XFD = 0x8000, /* 1000BASE-X FD Capable */ +}; + +enum { /* Scr page 0 */ + Pwrdown = 0x0004, /* power down */ + Mdix = 0x0060, /* MDI crossover mode */ + Endetect = 0x0300, /* energy detect */ +}; +enum { /* Scr page 2 */ + Rgmiipwrup = 0x0008, /* RGMII power up: must sw reset after */ +}; + +enum { /* Recr page 2 */ + Txtiming = 1<<4, + Rxtiming = 1<<5, +}; + +typedef struct Mii { + Lock; + int nphy; + int mask; + MiiPhy* phy[NMiiPhy]; + MiiPhy* curphy; + + void* ctlr; + int (*mir)(Mii*, int, int); + int (*miw)(Mii*, int, int, int); +} Mii; + +typedef struct MiiPhy { + Mii* mii; + int oui; + int phyno; + + int anar; + int fc; + int mscr; + + int link; + int speed; + int fd; + int rfc; + int tfc; +}; + +extern int mii(Mii*, int); +extern int miiane(Mii*, int, int, int); +extern int miimir(Mii*, int); +extern int miimiw(Mii*, int, int); +extern int miireset(Mii*); +extern int miistatus(Mii*); diff --git a/sys/src/9/kw/flashkw.c b/sys/src/9/kw/flashkw.c new file mode 100755 index 000000000..e45bc29de --- /dev/null +++ b/sys/src/9/kw/flashkw.c @@ -0,0 +1,671 @@ +/* + * sheevaplug nand flash driver + * + * for now separate from (inferno's) os/port/flashnand.c because the flash + * seems newer, and has different commands, but that is nand-chip specific, + * not sheevaplug-specific. they should be merged in future. + * + * the sheevaplug has a hynix 4gbit flash chip: hy27uf084g2m. + * 2048 byte pages, with 64 spare bytes each; erase block size is 128k. + * + * it has a "glueless" interface, at 0xf9000000. that's the address + * of the data register. the command and address registers are those + * or'ed with 1 and 2 respectively. + * + * linux uses this layout for the nand flash (from address 0 onwards): + * 1mb for u-boot + * 4mb for kernel + * 507mb for file system + * + * this is not so relevant here except for ecc. the first two areas + * (u-boot and kernel) are expected to have 4-bit ecc per 512 bytes + * (but calculated from last byte to first), bad erase blocks skipped. + * the file system area has 1-bit ecc per 256 bytes. + */ + +#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/flashif.h" +#include "../port/nandecc.h" + +enum { + Debug = 0, + + Nopage = ~0ul, /* cache is empty */ + + /* vendors */ + Hynix = 0xad, + Samsung = 0xec, + + /* chips */ + Hy27UF084G2M = 0xdc, + + NandActCEBoot = 1<<1, +}; + +typedef struct Nandreg Nandreg; +typedef struct Nandtab Nandtab; +typedef struct Cache Cache; + +struct Nandreg { /* hw registers */ + ulong rdparms; + ulong wrparms; + uchar _pad0[0x70 - 0x20]; + ulong ctl; +}; + +struct Nandtab { + int vid; + int did; + vlong size; + char* name; +}; + +struct Cache { + Flash *flif; + ulong pageno; + ulong pgsize; /* r->pagesize */ + char *page; /* of pgsize bytes */ +}; + +enum { + /* commands */ + Readstatus = 0x70, + Readid = 0x90, /* needs 1 0-address write */ + Resetf = 0xff, + + /* + * needs 5 address writes followed by Readstart, + * Readstartcache or Restartcopy. + */ + Read = 0x00, + Readstart = 0x30, + Readstartcache = 0x31, + Readstartcopy = 0x35, + /* after Readstartcache, to stop reading next pages */ + Readstopcache = 0x34, + + /* needs 5 address writes, the data, and -start or -cache */ + Program = 0x80, + Programstart = 0x10, + Programcache = 0x15, + + Copyback = 0x85, /* followed by Programstart */ + + /* 3 address writes for block followed by Erasestart */ + Erase = 0x60, + Erasestart = 0xd0, + + Randomread = 0x85, + Randomwrite = 0x05, + Randomwritestart= 0xe0, + + /* status bits */ + SFail = 1<<0, + SCachefail = 1<<1, + SIdle = 1<<5, /* doesn't seem to come on ever */ + SReady = 1<<6, + SNotprotected = 1<<7, + + Srdymask = SReady, /* was SIdle|SReady */ +}; + +Nandtab nandtab[] = { + {Hynix, Hy27UF084G2M, 512*MB, "Hy27UF084G2M"}, + {Samsung, 0xdc, 512*MB, "Samsung 2Gb"}, +}; + +static Cache cache; + +static void +nandcmd(Flash *f, uchar b) +{ + uchar *p = (uchar *)((ulong)f->addr|1); + + *p = b; + coherence(); +} + +static void +nandaddr(Flash *f, uchar b) +{ + uchar *p = (uchar *)((ulong)f->addr|2); + + *p = b; + coherence(); +} + +static uchar +nandread(Flash *f) +{ + return *(uchar *)f->addr; +} + +static void +nandreadn(Flash *f, uchar *buf, long n) +{ + uchar *p = f->addr; + + while(n-- > 0) + *buf++ = *p; +} + +static void +nandwrite(Flash *f, uchar b) +{ + *(uchar *)f->addr = b; + coherence(); +} + +static void +nandwriten(Flash *f, uchar *buf, long n) +{ + uchar *p = f->addr; + + while(n-- > 0) + *p = *buf++; + coherence(); +} + +static void +nandclaim(Flash*) +{ + Nandreg *nand = (Nandreg*)soc.nand; + + nand->ctl |= NandActCEBoot; + coherence(); +} + +static void +nandunclaim(Flash*) +{ + Nandreg *nand = (Nandreg*)soc.nand; + + nand->ctl &= ~NandActCEBoot; + coherence(); +} + + +void mmuidmap(uintptr phys, int mbs); + +Nandtab * +findflash(Flash *f, uintptr pa, uchar *id4p) +{ + int i; + ulong sts; + uchar maker, device, id3, id4; + Nandtab *chip; + + mmuidmap(pa, 16); + f->addr = (void *)pa; + + /* make sure controller is idle */ + nandclaim(f); + nandcmd(f, Resetf); + nandunclaim(f); + + nandclaim(f); + nandcmd(f, Readstatus); + sts = nandread(f); + nandunclaim(f); + for (i = 10; i > 0 && !(sts & SReady); i--) { + delay(50); + nandclaim(f); + nandcmd(f, Readstatus); + sts = nandread(f); + nandunclaim(f); + } + if(!(sts & SReady)) + return nil; + + nandclaim(f); + nandcmd(f, Readid); + nandaddr(f, 0); + maker = nandread(f); + device = nandread(f); + id3 = nandread(f); + USED(id3); + id4 = nandread(f); + nandunclaim(f); + if (id4p) + *id4p = id4; + + for(i = 0; i < nelem(nandtab); i++) { + chip = &nandtab[i]; + if(chip->vid == maker && chip->did == device) + return chip; + } + return nil; +} + +int +flashat(Flash *f, uintptr pa) +{ + return findflash(f, pa, nil) != nil; +} + +static int +idchip(Flash *f) +{ + uchar id4; + Flashregion *r; + Nandtab *chip; + static int blocksizes[4] = { 64*1024, 128*1024, 256*1024, 0 }; + static int pagesizes[4] = { 1024, 2*1024, 0, 0 }; + static int spares[2] = { 8, 16 }; /* per 512 bytes */ + + f->id = 0; + f->devid = 0; + f->width = 1; + chip = findflash(f, (uintptr)f->addr, &id4); + if (chip == nil) + return -1; + f->id = chip->vid; + f->devid = chip->did; + f->size = chip->size; + f->width = 1; + f->nr = 1; + + r = &f->regions[0]; + r->pagesize = pagesizes[id4 & MASK(2)]; + r->erasesize = blocksizes[(id4 >> 4) & MASK(2)]; + if (r->pagesize == 0 || r->erasesize == 0) { + iprint("flashkw: bogus flash sizes\n"); + return -1; + } + r->n = f->size / r->erasesize; + r->start = 0; + r->end = f->size; + assert(ispow2(r->pagesize)); + r->pageshift = log2(r->pagesize); + assert(ispow2(r->erasesize)); + r->eraseshift = log2(r->erasesize); + assert(r->eraseshift >= r->pageshift); + if (cache.page == nil) { + cache.pgsize = r->pagesize; + cache.page = smalloc(r->pagesize); + } + + r->spares = r->pagesize / 512 * spares[(id4 >> 2) & 1]; + print("#F0: kwnand: %s %,lud bytes pagesize %lud erasesize %,lud" + " spares per page %lud\n", chip->name, f->size, r->pagesize, + r->erasesize, r->spares); + return 0; +} + +static int +ctlrwait(Flash *f) +{ + int sts, cnt; + + nandclaim(f); + for (;;) { + nandcmd(f, Readstatus); + for(cnt = 100; cnt > 0 && (nandread(f) & Srdymask) != Srdymask; + cnt--) + microdelay(50); + nandcmd(f, Readstatus); + sts = nandread(f); + if((sts & Srdymask) == Srdymask) + break; + print("flashkw: flash ctlr busy, sts %#ux: resetting\n", sts); + nandcmd(f, Resetf); + } + nandunclaim(f); + return 0; +} + +static int +erasezone(Flash *f, Flashregion *r, ulong offset) +{ + int i; + ulong page, block; + uchar s; + + if (Debug) { + print("flashkw: erasezone: offset %#lux, region nblocks %d," + " start %#lux, end %#lux\n", offset, r->n, r->start, + r->end); + print(" erasesize %lud, pagesize %lud\n", + r->erasesize, r->pagesize); + } + assert(r->erasesize != 0); + if(offset & (r->erasesize - 1)) { + print("flashkw: erase offset %lud not block aligned\n", offset); + return -1; + } + page = offset >> r->pageshift; + block = page >> (r->eraseshift - r->pageshift); + if (Debug) + print("flashkw: erase: block %#lux\n", block); + + /* make sure controller is idle */ + if(ctlrwait(f) < 0) { + print("flashkw: erase: flash busy\n"); + return -1; + } + + /* start erasing */ + nandclaim(f); + nandcmd(f, Erase); + nandaddr(f, page>>0); + nandaddr(f, page>>8); + nandaddr(f, page>>16); + nandcmd(f, Erasestart); + + /* invalidate cache on any erasure (slight overkill) */ + cache.pageno = Nopage; + + /* have to wait until flash is done. typically ~2ms */ + delay(1); + nandcmd(f, Readstatus); + for(i = 0; i < 100; i++) { + s = nandread(f); + if(s & SReady) { + nandunclaim(f); + if(s & SFail) { + print("flashkw: erase: failed, block %#lux\n", + block); + return -1; + } + return 0; + } + microdelay(50); + } + print("flashkw: erase timeout, block %#lux\n", block); + nandunclaim(f); + return -1; +} + +static void +flcachepage(Flash *f, ulong page, uchar *buf) +{ + Flashregion *r = &f->regions[0]; + + assert(cache.pgsize == r->pagesize); + cache.flif = f; + cache.pageno = page; + /* permit i/o directly to or from the cache */ + if (buf != (uchar *)cache.page) + memmove(cache.page, buf, cache.pgsize); +} + +static int +write1page(Flash *f, ulong offset, void *buf) +{ + int i; + ulong page, v; + uchar s; + uchar *eccp, *p; + Flashregion *r = &f->regions[0]; + static uchar *oob; + + if (oob == nil) + oob = smalloc(r->spares); + + page = offset >> r->pageshift; + if (Debug) + print("flashkw: write nand offset %#lux page %#lux\n", + offset, page); + + if(offset & (r->pagesize - 1)) { + print("flashkw: write offset %lud not page aligned\n", offset); + return -1; + } + + p = buf; + memset(oob, 0xff, r->spares); + assert(r->spares >= 24); + eccp = oob + r->spares - 24; + for(i = 0; i < r->pagesize / 256; i++) { + v = nandecc(p); + *eccp++ = v>>8; + *eccp++ = v>>0; + *eccp++ = v>>16; + p += 256; + } + + if(ctlrwait(f) < 0) { + print("flashkw: write: nand not ready & idle\n"); + return -1; + } + + /* write, only whole pages for now, no sub-pages */ + nandclaim(f); + nandcmd(f, Program); + nandaddr(f, 0); + nandaddr(f, 0); + nandaddr(f, page>>0); + nandaddr(f, page>>8); + nandaddr(f, page>>16); + nandwriten(f, buf, r->pagesize); + nandwriten(f, oob, r->spares); + nandcmd(f, Programstart); + + microdelay(100); + nandcmd(f, Readstatus); + for(i = 0; i < 100; i++) { + s = nandread(f); + if(s & SReady) { + nandunclaim(f); + if(s & SFail) { + print("flashkw: write failed, page %#lux\n", + page); + return -1; + } + return 0; + } + microdelay(10); + } + + nandunclaim(f); + flcachepage(f, page, buf); + print("flashkw: write timeout for page %#lux\n", page); + return -1; +} + +static int +read1page(Flash *f, ulong offset, void *buf) +{ + int i; + ulong addr, page, w; + uchar *eccp, *p; + Flashregion *r = &f->regions[0]; + static uchar *oob; + + if (oob == nil) + oob = smalloc(r->spares); + + assert(r->pagesize != 0); + addr = offset & (r->pagesize - 1); + page = offset >> r->pageshift; + if(addr != 0) { + print("flashkw: read1page: read addr %#lux:" + " must read aligned page\n", addr); + return -1; + } + + /* satisfy request from cache if possible */ + if (f == cache.flif && page == cache.pageno && + r->pagesize == cache.pgsize) { + memmove(buf, cache.page, r->pagesize); + return 0; + } + + if (Debug) + print("flashkw: read offset %#lux addr %#lux page %#lux\n", + offset, addr, page); + + nandclaim(f); + nandcmd(f, Read); + nandaddr(f, addr>>0); + nandaddr(f, addr>>8); + nandaddr(f, page>>0); + nandaddr(f, page>>8); + nandaddr(f, page>>16); + nandcmd(f, Readstart); + + microdelay(50); + + nandreadn(f, buf, r->pagesize); + nandreadn(f, oob, r->spares); + + nandunclaim(f); + + /* verify/correct data. last 8*3 bytes is ecc, per 256 bytes. */ + p = buf; + assert(r->spares >= 24); + eccp = oob + r->spares - 24; + for(i = 0; i < r->pagesize / 256; i++) { + w = eccp[0] << 8 | eccp[1] << 0 | eccp[2] << 16; + eccp += 3; + switch(nandecccorrect(p, nandecc(p), &w, 1)) { + case NandEccErrorBad: + print("(page %d)\n", i); + return -1; + case NandEccErrorOneBit: + case NandEccErrorOneBitInEcc: + print("(page %d)\n", i); + /* fall through */ + case NandEccErrorGood: + break; + } + p += 256; + } + + flcachepage(f, page, buf); + return 0; +} + +/* + * read a page at offset into cache, copy fragment from buf into it + * at pagoff, and rewrite that page. + */ +static int +rewrite(Flash *f, ulong offset, ulong pagoff, void *buf, ulong size) +{ + if (read1page(f, offset, cache.page) < 0) + return -1; + memmove(&cache.page[pagoff], buf, size); + return write1page(f, offset, cache.page); +} + +/* there are no alignment constraints on offset, buf, nor n */ +static int +write(Flash *f, ulong offset, void *buf, long n) +{ + uint un, frag, pagoff; + ulong pgsize; + uchar *p; + Flashregion *r = &f->regions[0]; + + if(n <= 0) + panic("flashkw: write: non-positive count %ld", n); + un = n; + assert(r->pagesize != 0); + pgsize = r->pagesize; + + /* if a partial first page exists, update the first page with it. */ + p = buf; + pagoff = offset % pgsize; + if (pagoff != 0) { + frag = pgsize - pagoff; + if (frag > un) /* might not extend to end of page */ + frag = un; + if (rewrite(f, offset - pagoff, pagoff, p, frag) < 0) + return -1; + offset += frag; + p += frag; + un -= frag; + } + + /* copy whole pages */ + while (un >= pgsize) { + if (write1page(f, offset, p) < 0) + return -1; + offset += pgsize; + p += pgsize; + un -= pgsize; + } + + /* if a partial last page exists, update the last page with it. */ + if (un > 0) + return rewrite(f, offset, 0, p, un); + return 0; +} + +/* there are no alignment constraints on offset, buf, nor n */ +static int +read(Flash *f, ulong offset, void *buf, long n) +{ + uint un, frag, pagoff; + ulong pgsize; + uchar *p; + Flashregion *r = &f->regions[0]; + + if(n <= 0) + panic("flashkw: read: non-positive count %ld", n); + un = n; + assert(r->pagesize != 0); + pgsize = r->pagesize; + + /* if partial 1st page, read it into cache & copy fragment to buf */ + p = buf; + pagoff = offset % pgsize; + if (pagoff != 0) { + frag = pgsize - pagoff; + if (frag > un) /* might not extend to end of page */ + frag = un; + if (read1page(f, offset - pagoff, cache.page) < 0) + return -1; + offset += frag; + memmove(p, &cache.page[pagoff], frag); + p += frag; + un -= frag; + } + + /* copy whole pages */ + while (un >= pgsize) { + if (read1page(f, offset, p) < 0) + return -1; + offset += pgsize; + p += pgsize; + un -= pgsize; + } + + /* if partial last page, read into cache & copy initial fragment to buf */ + if (un > 0) { + if (read1page(f, offset, cache.page) < 0) + return -1; + memmove(p, cache.page, un); + } + return 0; +} + +static int +reset(Flash *f) +{ + if(f->data != nil) + return 1; + f->write = write; + f->read = read; + f->eraseall = nil; + f->erasezone = erasezone; + f->suspend = nil; + f->resume = nil; + f->sort = "nand"; + cache.pageno = Nopage; + return idchip(f); +} + +void +flashkwlink(void) +{ + addflashcard("nand", reset); +} diff --git a/sys/src/9/kw/fns.h b/sys/src/9/kw/fns.h new file mode 100755 index 000000000..fcaa647da --- /dev/null +++ b/sys/src/9/kw/fns.h @@ -0,0 +1,203 @@ +#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*, ...); +extern void uartkirkwoodconsole(void); +extern void serialputs(char *, int); +extern void serialputc(int c); + +#pragma varargck argpos _uartprint 1 + +extern void archreboot(void); +extern void archconfinit(void); +extern void archreset(void); +extern void barriers(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 cacheuwbinv(void); +extern uintptr cankaddr(uintptr pa); +extern void clockshutdown(void); +extern int clz(ulong); +int cmpswap(long*, long, long); + +#define coherence barriers + +extern u32int controlget(void); +extern u32int cpctget(void); +extern u32int cpidget(void); +extern char *cputype2name(char *, int); +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 u32int farget(void); +extern u32int fsrget(void); +extern int ispow2(uvlong); +extern void l1cachesoff(void); +extern void l1cacheson(void); +extern void l2cachecfgoff(void); +extern void l2cachecfgon(void); +extern void l2cacheon(void); +extern void l2cacheuinv(void); +extern void l2cacheuinvse(void*, int); +extern void l2cacheuwb(void); +extern void l2cacheuwbinv(void); +extern void l2cacheuwbinvse(void*, int); +extern void l2cacheuwbse(void*, int); +extern void lastresortprint(char *buf, long bp); +extern int log2(ulong); +extern void mmuinvalidate(void); /* 'mmu' or 'tlb'? */ +extern void mmuinvalidateaddr(u32int); /* 'mmu' or 'tlb'? */ +extern u32int pidget(void); +extern void pidput(u32int); +void procrestore(Proc *); +void procsave(Proc*); +void procsetup(Proc*); +extern void _reset(void); +extern void setr13(int, u32int*); +extern void syscallfmt(int syscallno, ulong pc, va_list list); +extern void sysretfmt(int syscallno, va_list list, long ret, uvlong start, uvlong stop); +extern int tas(void *); +extern u32int ttbget(void); +extern void ttbput(u32int); + +extern void intrclear(int sort, int v); +extern void intrenable(int sort, int v, void (*f)(Ureg*, void*), void *a, char *name); +extern void intrdisable(int sort, int v, void (*f)(Ureg*, void*), void* a, char *name); +extern void vectors(void); +extern void vtable(void); + +/* + * Things called in main. + */ +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*, Ureg *, 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 void* ucallocalign(usize size, int align, int span); +extern Block* ucallocb(int); +extern void ucfree(void*); +extern void ucfreeb(Block*); +extern Block* uciallocb(int); + +/* + * 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 spldone(void); +extern int splfhi(void); +extern int splflo(void); +extern void sysprocsetup(Proc*); + +/* + * PCI + */ +ulong pcibarsize(Pcidev*, int); +void pcibussize(Pcidev*, ulong*, ulong*); +int pcicfgr8(Pcidev*, int); +int pcicfgr16(Pcidev*, int); +int pcicfgr32(Pcidev*, int); +void pcicfgw8(Pcidev*, int, int); +void pcicfgw16(Pcidev*, int, int); +void pcicfgw32(Pcidev*, int, int); +void pciclrbme(Pcidev*); +void pciclrioe(Pcidev*); +void pciclrmwi(Pcidev*); +int pcigetpms(Pcidev*); +void pcihinv(Pcidev*); +uchar pciipin(Pcidev*, uchar); +Pcidev* pcimatch(Pcidev*, int, int); +Pcidev* pcimatchtbdf(int); +void pcireset(void); +int pciscan(int, Pcidev**); +void pcisetbme(Pcidev*); +void pcisetioe(Pcidev*); +void pcisetmwi(Pcidev*); +int pcisetpms(Pcidev*, int); +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])) + +/* + * this low-level printing stuff is ugly, + * but there appears to be no other way to + * print until after #t is populated. + */ +#define wave(c) { \ + coherence(); \ + while ((*(ulong *)(PHYSCONS+4*5) & (1<<5)) == 0) /* (x->lsr&LSRthre)==0? */ \ + ; \ + *(ulong *)PHYSCONS = (c); \ + coherence(); \ +} + +/* + * These are not good enough. + */ +#define KADDR(pa) UINT2PTR(KZERO|((uintptr)(pa))) +#define PADDR(va) PTR2UINT(((uintptr)(va)) & ~KSEGM) + +#define MASK(v) ((1UL << (v)) - 1) /* mask `v' bits wide */ diff --git a/sys/src/9/kw/fpi.c b/sys/src/9/kw/fpi.c new file mode 100755 index 000000000..f341f2e4a --- /dev/null +++ b/sys/src/9/kw/fpi.c @@ -0,0 +1,300 @@ +/* + * Floating Point Interpreter. + * shamelessly stolen from an original by ark. + */ +#include "fpi.h" + +void +fpiround(Internal *i) +{ + unsigned long guard; + + guard = i->l & GuardMask; + i->l &= ~GuardMask; + if(guard > (LsBit>>1) || (guard == (LsBit>>1) && (i->l & LsBit))){ + i->l += LsBit; + if(i->l & CarryBit){ + i->l &= ~CarryBit; + i->h++; + if(i->h & CarryBit){ + if (i->h & 0x01) + i->l |= CarryBit; + i->l >>= 1; + i->h >>= 1; + i->e++; + } + } + } +} + +static void +matchexponents(Internal *x, Internal *y) +{ + int count; + + count = y->e - x->e; + x->e = y->e; + if(count >= 2*FractBits){ + x->l = x->l || x->h; + x->h = 0; + return; + } + if(count >= FractBits){ + count -= FractBits; + x->l = x->h|(x->l != 0); + x->h = 0; + } + while(count > 0){ + count--; + if(x->h & 0x01) + x->l |= CarryBit; + if(x->l & 0x01) + x->l |= 2; + x->l >>= 1; + x->h >>= 1; + } +} + +static void +shift(Internal *i) +{ + i->e--; + i->h <<= 1; + i->l <<= 1; + if(i->l & CarryBit){ + i->l &= ~CarryBit; + i->h |= 0x01; + } +} + +static void +normalise(Internal *i) +{ + while((i->h & HiddenBit) == 0) + shift(i); +} + +static void +renormalise(Internal *i) +{ + if(i->e < -2 * FractBits) + i->e = -2 * FractBits; + while(i->e < 1){ + i->e++; + if(i->h & 0x01) + i->l |= CarryBit; + i->h >>= 1; + i->l = (i->l>>1)|(i->l & 0x01); + } + if(i->e >= ExpInfinity) + SetInfinity(i); +} + +void +fpinormalise(Internal *x) +{ + if(!IsWeird(x) && !IsZero(x)) + normalise(x); +} + +void +fpiadd(Internal *x, Internal *y, Internal *i) +{ + Internal *t; + + i->s = x->s; + if(IsWeird(x) || IsWeird(y)){ + if(IsNaN(x) || IsNaN(y)) + SetQNaN(i); + else + SetInfinity(i); + return; + } + if(x->e > y->e){ + t = x; + x = y; + y = t; + } + matchexponents(x, y); + i->e = x->e; + i->h = x->h + y->h; + i->l = x->l + y->l; + if(i->l & CarryBit){ + i->h++; + i->l &= ~CarryBit; + } + if(i->h & (HiddenBit<<1)){ + if(i->h & 0x01) + i->l |= CarryBit; + i->l = (i->l>>1)|(i->l & 0x01); + i->h >>= 1; + i->e++; + } + if(IsWeird(i)) + SetInfinity(i); +} + +void +fpisub(Internal *x, Internal *y, Internal *i) +{ + Internal *t; + + if(y->e < x->e + || (y->e == x->e && (y->h < x->h || (y->h == x->h && y->l < x->l)))){ + t = x; + x = y; + y = t; + } + i->s = y->s; + if(IsNaN(y)){ + SetQNaN(i); + return; + } + if(IsInfinity(y)){ + if(IsInfinity(x)) + SetQNaN(i); + else + SetInfinity(i); + return; + } + matchexponents(x, y); + i->e = y->e; + i->h = y->h - x->h; + i->l = y->l - x->l; + if(i->l < 0){ + i->l += CarryBit; + i->h--; + } + if(i->h == 0 && i->l == 0) + SetZero(i); + else while(i->e > 1 && (i->h & HiddenBit) == 0) + shift(i); +} + +#define CHUNK (FractBits/2) +#define CMASK ((1<<CHUNK)-1) +#define HI(x) ((short)((x)>>CHUNK) & CMASK) +#define LO(x) ((short)(x) & CMASK) +#define SPILL(x) ((x)>>CHUNK) +#define M(x, y) ((long)a[x]*(long)b[y]) +#define C(h, l) (((long)((h) & CMASK)<<CHUNK)|((l) & CMASK)) + +void +fpimul(Internal *x, Internal *y, Internal *i) +{ + long a[4], b[4], c[7], f[4]; + + i->s = x->s^y->s; + if(IsWeird(x) || IsWeird(y)){ + if(IsNaN(x) || IsNaN(y) || IsZero(x) || IsZero(y)) + SetQNaN(i); + else + SetInfinity(i); + return; + } + else if(IsZero(x) || IsZero(y)){ + SetZero(i); + return; + } + normalise(x); + normalise(y); + i->e = x->e + y->e - (ExpBias - 1); + + a[0] = HI(x->h); b[0] = HI(y->h); + a[1] = LO(x->h); b[1] = LO(y->h); + a[2] = HI(x->l); b[2] = HI(y->l); + a[3] = LO(x->l); b[3] = LO(y->l); + + c[6] = M(3, 3); + c[5] = M(2, 3) + M(3, 2) + SPILL(c[6]); + c[4] = M(1, 3) + M(2, 2) + M(3, 1) + SPILL(c[5]); + c[3] = M(0, 3) + M(1, 2) + M(2, 1) + M(3, 0) + SPILL(c[4]); + c[2] = M(0, 2) + M(1, 1) + M(2, 0) + SPILL(c[3]); + c[1] = M(0, 1) + M(1, 0) + SPILL(c[2]); + c[0] = M(0, 0) + SPILL(c[1]); + + f[0] = c[0]; + f[1] = C(c[1], c[2]); + f[2] = C(c[3], c[4]); + f[3] = C(c[5], c[6]); + + if((f[0] & HiddenBit) == 0){ + f[0] <<= 1; + f[1] <<= 1; + f[2] <<= 1; + f[3] <<= 1; + if(f[1] & CarryBit){ + f[0] |= 1; + f[1] &= ~CarryBit; + } + if(f[2] & CarryBit){ + f[1] |= 1; + f[2] &= ~CarryBit; + } + if(f[3] & CarryBit){ + f[2] |= 1; + f[3] &= ~CarryBit; + } + i->e--; + } + i->h = f[0]; + i->l = f[1]; + if(f[2] || f[3]) + i->l |= 1; + renormalise(i); +} + +void +fpidiv(Internal *x, Internal *y, Internal *i) +{ + i->s = x->s^y->s; + if(IsNaN(x) || IsNaN(y) + || (IsInfinity(x) && IsInfinity(y)) || (IsZero(x) && IsZero(y))){ + SetQNaN(i); + return; + } + else if(IsZero(x) || IsInfinity(y)){ + SetInfinity(i); + return; + } + else if(IsInfinity(x) || IsZero(y)){ + SetZero(i); + return; + } + normalise(x); + normalise(y); + i->h = 0; + i->l = 0; + i->e = y->e - x->e + (ExpBias + 2*FractBits - 1); + do{ + if(y->h > x->h || (y->h == x->h && y->l >= x->l)){ + i->l |= 0x01; + y->h -= x->h; + y->l -= x->l; + if(y->l < 0){ + y->l += CarryBit; + y->h--; + } + } + shift(y); + shift(i); + }while ((i->h & HiddenBit) == 0); + if(y->h || y->l) + i->l |= 0x01; + renormalise(i); +} + +int +fpicmp(Internal *x, Internal *y) +{ + if(IsNaN(x) && IsNaN(y)) + return 0; + if(IsInfinity(x) && IsInfinity(y)) + return y->s - x->s; + if(x->e == y->e && x->h == y->h && x->l == y->l) + return y->s - x->s; + if(x->e < y->e + || (x->e == y->e && (x->h < y->h || (x->h == y->h && x->l < y->l)))) + return y->s ? 1: -1; + return x->s ? -1: 1; +} diff --git a/sys/src/9/kw/fpi.h b/sys/src/9/kw/fpi.h new file mode 100755 index 000000000..abaa7c120 --- /dev/null +++ b/sys/src/9/kw/fpi.h @@ -0,0 +1,61 @@ +typedef long Word; +typedef unsigned long Single; +typedef struct { + unsigned long l; + unsigned long h; +} Double; + +enum { + FractBits = 28, + CarryBit = 0x10000000, + HiddenBit = 0x08000000, + MsBit = HiddenBit, + NGuardBits = 3, + GuardMask = 0x07, + LsBit = (1<<NGuardBits), + + SingleExpBias = 127, + SingleExpMax = 255, + DoubleExpBias = 1023, + DoubleExpMax = 2047, + + ExpBias = DoubleExpBias, + ExpInfinity = DoubleExpMax, +}; + +typedef struct { + unsigned char s; + short e; + long l; /* 0000FFFFFFFFFFFFFFFFFFFFFFFFFGGG */ + long h; /* 0000HFFFFFFFFFFFFFFFFFFFFFFFFFFF */ +} Internal; + +#define IsWeird(n) ((n)->e >= ExpInfinity) +#define IsInfinity(n) (IsWeird(n) && (n)->h == HiddenBit && (n)->l == 0) +#define SetInfinity(n) ((n)->e = ExpInfinity, (n)->h = HiddenBit, (n)->l = 0) +#define IsNaN(n) (IsWeird(n) && (((n)->h & ~HiddenBit) || (n)->l)) +#define SetQNaN(n) ((n)->s = 0, (n)->e = ExpInfinity, \ + (n)->h = HiddenBit|(LsBit<<1), (n)->l = 0) +#define IsZero(n) ((n)->e == 1 && (n)->h == 0 && (n)->l == 0) +#define SetZero(n) ((n)->e = 1, (n)->h = 0, (n)->l = 0) + +/* + * fpi.c + */ +extern void fpiround(Internal *); +extern void fpiadd(Internal *, Internal *, Internal *); +extern void fpisub(Internal *, Internal *, Internal *); +extern void fpimul(Internal *, Internal *, Internal *); +extern void fpidiv(Internal *, Internal *, Internal *); +extern int fpicmp(Internal *, Internal *); +extern void fpinormalise(Internal*); + +/* + * fpimem.c + */ +extern void fpis2i(Internal *, void *); +extern void fpid2i(Internal *, void *); +extern void fpiw2i(Internal *, void *); +extern void fpii2s(void *, Internal *); +extern void fpii2d(void *, Internal *); +extern void fpii2w(Word *, Internal *); diff --git a/sys/src/9/kw/fpiarm.c b/sys/src/9/kw/fpiarm.c new file mode 100755 index 000000000..063d10c52 --- /dev/null +++ b/sys/src/9/kw/fpiarm.c @@ -0,0 +1,576 @@ +/* + * this doesn't attempt to implement ARM floating-point properties + * that aren't visible in the Inferno environment. + * all arithmetic is done in double precision. + * the FP trap status isn't updated. + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" + +#include "ureg.h" + +#include "arm.h" +#include "fpi.h" + +/* undef this if correct kernel r13 isn't in Ureg; + * check calculation in fpiarm below + */ + + +#define REG(ur, x) (*(long*)(((char*)(ur))+roff[(x)])) +#define FR(ufp, x) (*(Internal*)(ufp)->regs[(x)&7]) + +typedef struct FP2 FP2; +typedef struct FP1 FP1; + +struct FP2 { + char* name; + void (*f)(Internal, Internal, Internal*); +}; + +struct FP1 { + char* name; + void (*f)(Internal*, Internal*); +}; + +enum { + N = 1<<31, + Z = 1<<30, + C = 1<<29, + V = 1<<28, + REGPC = 15, +}; + +enum { + fpemudebug = 0, +}; + +#undef OFR +#define OFR(X) ((ulong)&((Ureg*)0)->X) + +static int roff[] = { + OFR(r0), OFR(r1), OFR(r2), OFR(r3), + OFR(r4), OFR(r5), OFR(r6), OFR(r7), + OFR(r8), OFR(r9), OFR(r10), OFR(r11), + OFR(r12), OFR(r13), OFR(r14), OFR(pc), +}; + +static Internal fpconst[8] = { /* indexed by op&7 */ + /* s, e, l, h */ + {0, 0x1, 0x00000000, 0x00000000}, /* 0.0 */ + {0, 0x3FF, 0x00000000, 0x08000000}, /* 1.0 */ + {0, 0x400, 0x00000000, 0x08000000}, /* 2.0 */ + {0, 0x400, 0x00000000, 0x0C000000}, /* 3.0 */ + {0, 0x401, 0x00000000, 0x08000000}, /* 4.0 */ + {0, 0x401, 0x00000000, 0x0A000000}, /* 5.0 */ + {0, 0x3FE, 0x00000000, 0x08000000}, /* 0.5 */ + {0, 0x402, 0x00000000, 0x0A000000}, /* 10.0 */ +}; + +/* + * arm binary operations + */ + +static void +fadd(Internal m, Internal n, Internal *d) +{ + (m.s == n.s? fpiadd: fpisub)(&m, &n, d); +} + +static void +fsub(Internal m, Internal n, Internal *d) +{ + m.s ^= 1; + (m.s == n.s? fpiadd: fpisub)(&m, &n, d); +} + +static void +fsubr(Internal m, Internal n, Internal *d) +{ + n.s ^= 1; + (n.s == m.s? fpiadd: fpisub)(&n, &m, d); +} + +static void +fmul(Internal m, Internal n, Internal *d) +{ + fpimul(&m, &n, d); +} + +static void +fdiv(Internal m, Internal n, Internal *d) +{ + fpidiv(&m, &n, d); +} + +static void +fdivr(Internal m, Internal n, Internal *d) +{ + fpidiv(&n, &m, d); +} + +/* + * arm unary operations + */ + +static void +fmov(Internal *m, Internal *d) +{ + *d = *m; +} + +static void +fmovn(Internal *m, Internal *d) +{ + *d = *m; + d->s ^= 1; +} + +static void +fabsf(Internal *m, Internal *d) +{ + *d = *m; + d->s = 0; +} + +static void +frnd(Internal *m, Internal *d) +{ + short e; + + (m->s? fsub: fadd)(fpconst[6], *m, d); + if(IsWeird(d)) + return; + fpiround(d); + e = (d->e - ExpBias) + 1; + if(e <= 0) + SetZero(d); + else if(e > FractBits){ + if(e < 2*FractBits) + d->l &= ~((1<<(2*FractBits - e))-1); + }else{ + d->l = 0; + if(e < FractBits) + d->h &= ~((1<<(FractBits-e))-1); + } +} + +static FP1 optab1[16] = { /* Fd := OP Fm */ +[0] {"MOVF", fmov}, +[1] {"NEGF", fmovn}, +[2] {"ABSF", fabsf}, +[3] {"RNDF", frnd}, +[4] {"SQTF", /*fsqt*/0}, +/* LOG, LGN, EXP, SIN, COS, TAN, ASN, ACS, ATN all `deprecated' */ +/* URD and NRM aren't implemented */ +}; + +static FP2 optab2[16] = { /* Fd := Fn OP Fm */ +[0] {"ADDF", fadd}, +[1] {"MULF", fmul}, +[2] {"SUBF", fsub}, +[3] {"RSUBF", fsubr}, +[4] {"DIVF", fdiv}, +[5] {"RDIVF", fdivr}, +/* POW, RPW deprecated */ +[8] {"REMF", /*frem*/0}, +[9] {"FMF", fmul}, /* fast multiply */ +[10] {"FDV", fdiv}, /* fast divide */ +[11] {"FRD", fdivr}, /* fast reverse divide */ +/* POL deprecated */ +}; + +static ulong +fcmp(Internal *n, Internal *m) +{ + int i; + Internal rm, rn; + + if(IsWeird(m) || IsWeird(n)){ + /* BUG: should trap if not masked */ + return V|C; + } + rn = *n; + rm = *m; + fpiround(&rn); + fpiround(&rm); + i = fpicmp(&rn, &rm); + if(i > 0) + return C; + else if(i == 0) + return C|Z; + else + return N; +} + +static void +fld(void (*f)(Internal*, void*), int d, ulong ea, int n, FPsave *ufp) +{ + void *mem; + + mem = (void*)ea; + (*f)(&FR(ufp, d), mem); + if(fpemudebug) + print("MOV%c #%lux, F%d\n", n==8? 'D': 'F', ea, d); +} + +static void +fst(void (*f)(void*, Internal*), ulong ea, int s, int n, FPsave *ufp) +{ + Internal tmp; + void *mem; + + mem = (void*)ea; + tmp = FR(ufp, s); + if(fpemudebug) + print("MOV%c F%d,#%lux\n", n==8? 'D': 'F', s, ea); + (*f)(mem, &tmp); +} + +static int +condok(int cc, int c) +{ + switch(c){ + case 0: /* Z set */ + return cc&Z; + case 1: /* Z clear */ + return (cc&Z) == 0; + case 2: /* C set */ + return cc&C; + case 3: /* C clear */ + return (cc&C) == 0; + case 4: /* N set */ + return cc&N; + case 5: /* N clear */ + return (cc&N) == 0; + case 6: /* V set */ + return cc&V; + case 7: /* V clear */ + return (cc&V) == 0; + case 8: /* C set and Z clear */ + return cc&C && (cc&Z) == 0; + case 9: /* C clear or Z set */ + return (cc&C) == 0 || cc&Z; + case 10: /* N set and V set, or N clear and V clear */ + return (~cc&(N|V))==0 || (cc&(N|V)) == 0; + case 11: /* N set and V clear, or N clear and V set */ + return (cc&(N|V))==N || (cc&(N|V))==V; + case 12: /* Z clear, and either N set and V set or N clear and V clear */ + return (cc&Z) == 0 && ((~cc&(N|V))==0 || (cc&(N|V))==0); + case 13: /* Z set, or N set and V clear or N clear and V set */ + return (cc&Z) || (cc&(N|V))==N || (cc&(N|V))==V; + case 14: /* always */ + return 1; + case 15: /* never (reserved) */ + return 0; + } + return 0; /* not reached */ +} + +static void +unimp(ulong pc, ulong op) +{ + char buf[60]; + + snprint(buf, sizeof(buf), "sys: fp: pc=%lux unimp fp 0x%.8lux", pc, op); + if(fpemudebug) + print("FPE: %s\n", buf); + error(buf); + /* no return */ +} + +static void +fpemu(ulong pc, ulong op, Ureg *ur, FPsave *ufp) +{ + int rn, rd, tag, o; + long off; + ulong ea; + Internal tmp, *fm, *fn; + + /* note: would update fault status here if we noted numeric exceptions */ + + /* + * LDF, STF; 10.1.1 + */ + if(((op>>25)&7) == 6){ + if(op & (1<<22)) + unimp(pc, op); /* packed or extended */ + rn = (op>>16)&0xF; + off = (op&0xFF)<<2; + if((op & (1<<23)) == 0) + off = -off; + ea = REG(ur, rn); + if(rn == REGPC) + ea += 8; + if(op & (1<<24)) + ea += off; + rd = (op>>12)&7; + if(op & (1<<20)){ + if(op & (1<<15)) + fld(fpid2i, rd, ea, 8, ufp); + else + fld(fpis2i, rd, ea, 4, ufp); + }else{ + if(op & (1<<15)) + fst(fpii2d, ea, rd, 8, ufp); + else + fst(fpii2s, ea, rd, 4, ufp); + } + if((op & (1<<24)) == 0) + ea += off; + if(op & (1<<21)) + REG(ur, rn) = ea; + return; + } + + /* + * CPRT/transfer, 10.3 + */ + if(op & (1<<4)){ + rd = (op>>12) & 0xF; + + /* + * compare, 10.3.1 + */ + if(rd == 15 && op & (1<<20)){ + rn = (op>>16)&7; + fn = &FR(ufp, rn); + if(op & (1<<3)){ + fm = &fpconst[op&7]; + if(fpemudebug) + tag = 'C'; + }else{ + fm = &FR(ufp, op&7); + if(fpemudebug) + tag = 'F'; + } + switch((op>>21)&7){ + default: + unimp(pc, op); + case 4: /* CMF: Fn :: Fm */ + case 6: /* CMFE: Fn :: Fm (with exception) */ + ur->psr &= ~(N|C|Z|V); + ur->psr |= fcmp(fn, fm); + break; + case 5: /* CNF: Fn :: -Fm */ + case 7: /* CNFE: Fn :: -Fm (with exception) */ + tmp = *fm; + tmp.s ^= 1; + ur->psr &= ~(N|C|Z|V); + ur->psr |= fcmp(fn, &tmp); + break; + } + if(fpemudebug) + print("CMPF %c%d,F%ld =%#lux\n", + tag, rn, op&7, ur->psr>>28); + return; + } + + /* + * other transfer, 10.3 + */ + switch((op>>20)&0xF){ + default: + unimp(pc, op); + case 0: /* FLT */ + rn = (op>>16) & 7; + fpiw2i(&FR(ufp, rn), ®(ur, rd)); + if(fpemudebug) + print("MOVW[FD] R%d, F%d\n", rd, rn); + break; + case 1: /* FIX */ + if(op & (1<<3)) + unimp(pc, op); + rn = op & 7; + tmp = FR(ufp, rn); + fpii2w(®(ur, rd), &tmp); + if(fpemudebug) + print("MOV[FD]W F%d, R%d =%ld\n", rn, rd, REG(ur, rd)); + break; + case 2: /* FPSR := Rd */ + ufp->status = REG(ur, rd); + if(fpemudebug) + print("MOVW R%d, FPSR\n", rd); + break; + case 3: /* Rd := FPSR */ + REG(ur, rd) = ufp->status; + if(fpemudebug) + print("MOVW FPSR, R%d\n", rd); + break; + case 4: /* FPCR := Rd */ + ufp->control = REG(ur, rd); + if(fpemudebug) + print("MOVW R%d, FPCR\n", rd); + break; + case 5: /* Rd := FPCR */ + REG(ur, rd) = ufp->control; + if(fpemudebug) + print("MOVW FPCR, R%d\n", rd); + break; + } + return; + } + + /* + * arithmetic + */ + + if(op & (1<<3)){ /* constant */ + fm = &fpconst[op&7]; + if(fpemudebug) + tag = 'C'; + }else{ + fm = &FR(ufp, op&7); + if(fpemudebug) + tag = 'F'; + } + rd = (op>>12)&7; + o = (op>>20)&0xF; + if(op & (1<<15)){ /* monadic */ + FP1 *fp; + fp = &optab1[o]; + if(fp->f == nil) + unimp(pc, op); + if(fpemudebug) + print("%s %c%ld,F%d\n", fp->name, tag, op&7, rd); + (*fp->f)(fm, &FR(ufp, rd)); + } else { + FP2 *fp; + fp = &optab2[o]; + if(fp->f == nil) + unimp(pc, op); + rn = (op>>16)&7; + if(fpemudebug) + print("%s %c%ld,F%d,F%d\n", fp->name, tag, op&7, rn, rd); + (*fp->f)(*fm, FR(ufp, rn), &FR(ufp, rd)); + } +} + +void +casemu(ulong pc, ulong op, Ureg *ur) +{ + ulong *rp, ro, rn, *rd; + + USED(pc); + + rp = (ulong*)ur; + ro = rp[op>>16 & 0x7]; + rn = rp[op>>0 & 0x7]; + rd = rp + (op>>12 & 0x7); + rp = (ulong*)*rd; + validaddr((ulong)rp, 4, 1); + splhi(); + if(*rd = (*rp == ro)) + *rp = rn; + spllo(); +} + +int ldrexvalid; + +void +ldrex(ulong pc, ulong op, Ureg *ur) +{ + ulong *rp, *rd, *addr; + + USED(pc); + + rp = (ulong*)ur; + rd = rp + (op>>16 & 0x7); + addr = (ulong*)*rd; + validaddr((ulong)addr, 4, 0); + ldrexvalid = 1; + rp[op>>12 & 0x7] = *addr; + if(fpemudebug) + print("ldrex, r%ld = [r%ld]@0x%8.8p = 0x%8.8lux", + op>>12 & 0x7, op>>16 & 0x7, addr, rp[op>>12 & 0x7]); +} + +void +strex(ulong pc, ulong op, Ureg *ur) +{ + ulong *rp, rn, *rd, *addr; + + USED(pc); + + rp = (ulong*)ur; + rd = rp + (op>>16 & 0x7); + rn = rp[op>>0 & 0x7]; + addr = (ulong*)*rd; + validaddr((ulong)addr, 4, 1); + splhi(); + if(ldrexvalid){ + if(fpemudebug) + print("strex valid, [r%ld]@0x%8.8p = r%ld = 0x%8.8lux", + op>>16 & 0x7, addr, op>>0 & 0x7, rn); + *addr = rn; + ldrexvalid = 0; + rp[op>>12 & 0x7] = 0; + }else{ + if(fpemudebug) + print("strex invalid, r%ld = 1", op>>16 & 0x7); + rp[op>>12 & 0x7] = 1; + } + spllo(); +} + +struct { + ulong opc; + ulong mask; + void (*f)(ulong, ulong, Ureg*); +} specialopc[] = { + { 0x01900f9f, 0x0ff00fff, ldrex }, + { 0x01800f90, 0x0ff00ff0, strex }, + { 0x0ed00100, 0x0ef08100, casemu }, + { 0x00000000, 0x00000000, nil } +}; + +/* + * returns the number of FP instructions emulated + */ +int +fpiarm(Ureg *ur) +{ + ulong op, o; + FPsave *ufp; + int i, n; + + if(up == nil) + panic("fpiarm not in a process"); + ufp = &up->fpsave; + /* because all the state is in the proc structure, + * it need not be saved/restored + */ + if(up->fpstate != FPactive){ +// assert(sizeof(Internal) == sizeof(ufp->regs[0])); + up->fpstate = FPactive; + ufp->control = 0; + ufp->status = (0x01<<28)|(1<<12); /* software emulation, alternative C flag */ + for(n = 0; n < 8; n++) + FR(ufp, n) = fpconst[0]; + } + for(n=0; ;n++){ + validaddr(ur->pc, 4, 0); + op = *(ulong*)(ur->pc); + if(fpemudebug) + print("%#lux: %#8.8lux ", ur->pc, op); + o = (op>>24) & 0xF; + if(condok(ur->psr, op>>28)){ + for(i = 0; specialopc[i].f; i++) + if((op & specialopc[i].mask) == specialopc[i].opc) + break; + if(specialopc[i].f) + specialopc[i].f(ur->pc, op, ur); + else if((op & 0xF00) != 0x100 || o != 0xE && (o&~1) != 0xC) + break; + else + fpemu(ur->pc, op, ur, ufp); + }else if((op & 0xF00) != 0x100 || o != 0xE && (o&~1) != 0xC) + break; + ur->pc += 4; + } + if(fpemudebug) print("\n"); + return n; +} diff --git a/sys/src/9/kw/fpimem.c b/sys/src/9/kw/fpimem.c new file mode 100755 index 000000000..627ab6355 --- /dev/null +++ b/sys/src/9/kw/fpimem.c @@ -0,0 +1,136 @@ +#include "fpi.h" + +/* + * the following routines depend on memory format, not the machine + */ + +void +fpis2i(Internal *i, void *v) +{ + Single *s = v; + + i->s = (*s & 0x80000000) ? 1: 0; + if((*s & ~0x80000000) == 0){ + SetZero(i); + return; + } + i->e = ((*s>>23) & 0x00FF) - SingleExpBias + ExpBias; + i->h = (*s & 0x007FFFFF)<<(1+NGuardBits); + i->l = 0; + if(i->e) + i->h |= HiddenBit; + else + i->e++; +} + +void +fpid2i(Internal *i, void *v) +{ + Double *d = v; + + i->s = (d->h & 0x80000000) ? 1: 0; + i->e = (d->h>>20) & 0x07FF; + i->h = ((d->h & 0x000FFFFF)<<(4+NGuardBits))|((d->l>>25) & 0x7F); + i->l = (d->l & 0x01FFFFFF)<<NGuardBits; + if(i->e) + i->h |= HiddenBit; + else + i->e++; +} + +void +fpiw2i(Internal *i, void *v) +{ + Word w, word = *(Word*)v; + short e; + + if(word < 0){ + i->s = 1; + word = -word; + } + else + i->s = 0; + if(word == 0){ + SetZero(i); + return; + } + if(word > 0){ + for (e = 0, w = word; w; w >>= 1, e++) + ; + } else + e = 32; + if(e > FractBits){ + i->h = word>>(e - FractBits); + i->l = (word & ((1<<(e - FractBits)) - 1))<<(2*FractBits - e); + } + else { + i->h = word<<(FractBits - e); + i->l = 0; + } + i->e = (e - 1) + ExpBias; +} + +void +fpii2s(void *v, Internal *i) +{ + short e; + Single *s = (Single*)v; + + fpiround(i); + if(i->h & HiddenBit) + i->h &= ~HiddenBit; + else + i->e--; + *s = i->s ? 0x80000000: 0; + e = i->e; + if(e < ExpBias){ + if(e <= (ExpBias - SingleExpBias)) + return; + e = SingleExpBias - (ExpBias - e); + } + else if(e >= (ExpBias + (SingleExpMax-SingleExpBias))){ + *s |= SingleExpMax<<23; + return; + } + else + e = SingleExpBias + (e - ExpBias); + *s |= (e<<23)|(i->h>>(1+NGuardBits)); +} + +void +fpii2d(void *v, Internal *i) +{ + Double *d = (Double*)v; + + fpiround(i); + if(i->h & HiddenBit) + i->h &= ~HiddenBit; + else + i->e--; + i->l = ((i->h & GuardMask)<<25)|(i->l>>NGuardBits); + i->h >>= NGuardBits; + d->h = i->s ? 0x80000000: 0; + d->h |= (i->e<<20)|((i->h & 0x00FFFFFF)>>4); + d->l = (i->h<<28)|i->l; +} + +void +fpii2w(Word *word, Internal *i) +{ + Word w; + short e; + + fpiround(i); + e = (i->e - ExpBias) + 1; + if(e <= 0) + w = 0; + else if(e > 31) + w = 0x7FFFFFFF; + else if(e > FractBits) + w = (i->h<<(e - FractBits))|(i->l>>(2*FractBits - e)); + else + w = i->h>>(FractBits-e); + if(i->s) + w = -w; + *word = w; +} diff --git a/sys/src/9/kw/init9.s b/sys/src/9/kw/init9.s new file mode 100755 index 000000000..1d7f2bec3 --- /dev/null +++ b/sys/src/9/kw/init9.s @@ -0,0 +1,25 @@ +/* + * This is the same as the C programme: + * + * void + * main(char* argv0) + * { + * startboot(argv0, &argv0); + * } + * + * It is in assembler because SB needs to be + * set and doing this in C drags in too many + * other routines. + */ +TEXT main(SB), 1, $8 + MOVW $setR12(SB), R12 /* load the SB */ + MOVW $boot(SB), R0 + + ADD $12, R13, R1 /* pointer to 0(FP) */ + + MOVW R0, 4(R13) /* pass argc, argv */ + MOVW R1, 8(R13) + + BL startboot(SB) +_loop: + B _loop diff --git a/sys/src/9/kw/io.h b/sys/src/9/kw/io.h new file mode 100755 index 000000000..f1ad24689 --- /dev/null +++ b/sys/src/9/kw/io.h @@ -0,0 +1,425 @@ +enum { + BusCBUS = 0, /* Corollary CBUS */ + BusCBUSII, /* Corollary CBUS II */ + BusEISA, /* Extended ISA */ + BusFUTURE, /* IEEE Futurebus */ + BusINTERN, /* Internal bus */ + BusISA, /* Industry Standard Architecture */ + BusMBI, /* Multibus I */ + BusMBII, /* Multibus II */ + BusMCA, /* Micro Channel Architecture */ + BusMPI, /* MPI */ + BusMPSA, /* MPSA */ + BusNUBUS, /* Apple Macintosh NuBus */ + BusPCI, /* Peripheral Component Interconnect */ + BusPCMCIA, /* PC Memory Card International Association */ + BusTC, /* DEC TurboChannel */ + BusVL, /* VESA Local bus */ + BusVME, /* VMEbus */ + BusXPRESS, /* Express System Bus */ + BUSUNKNOWN = -1 +}; + +#define MKBUS(t,b,d,f) (((t)<<24)|(((b)&0xFF)<<16)|(((d)&0x1F)<<11)|(((f)&0x07)<<8)) +#define BUSFNO(tbdf) (((tbdf)>>8)&0x07) +#define BUSDNO(tbdf) (((tbdf)>>11)&0x1F) +#define BUSBNO(tbdf) (((tbdf)>>16)&0xFF) +#define BUSTYPE(tbdf) ((tbdf)>>24) +#define BUSBDF(tbdf) ((tbdf)&0x00FFFF00) + +/* + * PCI support code. + */ +enum { /* type 0 & type 1 pre-defined header */ + PciVID = 0x00, /* vendor ID */ + PciDID = 0x02, /* device ID */ + PciPCR = 0x04, /* command */ + PciPSR = 0x06, /* status */ + PciRID = 0x08, /* revision ID */ + PciCCRp = 0x09, /* programming interface class code */ + PciCCRu = 0x0A, /* sub-class code */ + PciCCRb = 0x0B, /* base class code */ + PciCLS = 0x0C, /* cache line size */ + PciLTR = 0x0D, /* latency timer */ + PciHDT = 0x0E, /* header type */ + PciBST = 0x0F, /* BIST */ +}; + +/* ccrb (base class code) values; controller types */ +enum { + Pcibcpci1 = 0, /* pci 1.0; no class codes defined */ + Pcibcstore = 1, /* mass storage */ + Pcibcnet = 2, /* network */ + Pcibcdisp = 3, /* display */ + Pcibcmmedia = 4, /* multimedia */ + Pcibcmem = 5, /* memory */ + Pcibcbridge = 6, /* bridge */ + Pcibccomm = 7, /* simple comms (e.g., serial) */ + Pcibcbasesys = 8, /* base system */ + Pcibcinput = 9, /* input */ + Pcibcdock = 0xa, /* docking stations */ + Pcibcproc = 0xb, /* processors */ + Pcibcserial = 0xc, /* serial bus (e.g., USB) */ + Pcibcwireless = 0xd, /* wireless */ + Pcibcintell = 0xe, /* intelligent i/o */ + Pcibcsatcom = 0xf, /* satellite comms */ + Pcibccrypto = 0x10, /* encryption/decryption */ + Pcibcdacq = 0x11, /* data acquisition & signal proc. */ +}; + +/* ccru (sub-class code) values; common cases only */ +enum { + /* mass storage */ + Pciscscsi = 0, /* SCSI */ + Pciscide = 1, /* IDE (ATA) */ + + /* network */ + Pciscether = 0, /* Ethernet */ + + /* display */ + Pciscvga = 0, /* VGA */ + Pciscxga = 1, /* XGA */ + Pcisc3d = 2, /* 3D */ + + /* bridges */ + Pcischostpci = 0, /* host/pci */ + Pciscpcicpci = 1, /* pci/pci */ + + /* simple comms */ + Pciscserial = 0, /* 16450, etc. */ + Pciscmultiser = 1, /* multiport serial */ + + /* serial bus */ + Pciscusb = 3, /* USB */ +}; + +enum { /* type 0 pre-defined header */ + PciCIS = 0x28, /* cardbus CIS pointer */ + PciSVID = 0x2C, /* subsystem vendor ID */ + PciSID = 0x2E, /* cardbus CIS pointer */ + PciEBAR0 = 0x30, /* expansion ROM base address */ + PciMGNT = 0x3E, /* burst period length */ + PciMLT = 0x3F, /* maximum latency between bursts */ +}; + +enum { /* type 1 pre-defined header */ + PciPBN = 0x18, /* primary bus number */ + PciSBN = 0x19, /* secondary bus number */ + PciUBN = 0x1A, /* subordinate bus number */ + PciSLTR = 0x1B, /* secondary latency timer */ + PciIBR = 0x1C, /* I/O base */ + PciILR = 0x1D, /* I/O limit */ + PciSPSR = 0x1E, /* secondary status */ + PciMBR = 0x20, /* memory base */ + PciMLR = 0x22, /* memory limit */ + PciPMBR = 0x24, /* prefetchable memory base */ + PciPMLR = 0x26, /* prefetchable memory limit */ + PciPUBR = 0x28, /* prefetchable base upper 32 bits */ + PciPULR = 0x2C, /* prefetchable limit upper 32 bits */ + PciIUBR = 0x30, /* I/O base upper 16 bits */ + PciIULR = 0x32, /* I/O limit upper 16 bits */ + PciEBAR1 = 0x28, /* expansion ROM base address */ + PciBCR = 0x3E, /* bridge control register */ +}; + +enum { /* type 2 pre-defined header */ + PciCBExCA = 0x10, + PciCBSPSR = 0x16, + PciCBPBN = 0x18, /* primary bus number */ + PciCBSBN = 0x19, /* secondary bus number */ + PciCBUBN = 0x1A, /* subordinate bus number */ + PciCBSLTR = 0x1B, /* secondary latency timer */ + PciCBMBR0 = 0x1C, + PciCBMLR0 = 0x20, + PciCBMBR1 = 0x24, + PciCBMLR1 = 0x28, + PciCBIBR0 = 0x2C, /* I/O base */ + PciCBILR0 = 0x30, /* I/O limit */ + PciCBIBR1 = 0x34, /* I/O base */ + PciCBILR1 = 0x38, /* I/O limit */ + PciCBSVID = 0x40, /* subsystem vendor ID */ + PciCBSID = 0x42, /* subsystem ID */ + PciCBLMBAR = 0x44, /* legacy mode base address */ +}; + +typedef struct Pcisiz Pcisiz; +struct Pcisiz +{ + Pcidev* dev; + int siz; + int bar; +}; + +typedef struct Pcidev Pcidev; +struct Pcidev +{ + int tbdf; /* type+bus+device+function */ + ushort vid; /* vendor ID */ + ushort did; /* device ID */ + + ushort pcr; + + uchar rid; + uchar ccrp; + uchar ccru; + uchar ccrb; + uchar cls; + uchar ltr; + + struct { + ulong bar; /* base address */ + int size; + } mem[6]; + + struct { + ulong bar; + int size; + } rom; + uchar intl; /* interrupt line */ + + Pcidev* list; + Pcidev* link; /* next device on this bno */ + + Pcidev* bridge; /* down a bus */ + struct { + ulong bar; + int size; + } ioa, mema; + + int pmrb; /* power management register block */ +}; + +#define PCIWINDOW 0 +#define PCIWADDR(va) (PADDR(va)+PCIWINDOW) + +/* + * Kirkwood stuff + */ + +enum { + AddrEfuse = PHYSIO+0x1008c, + Addrpci = PHYSIO+0x40000, /* for registers below */ + Addrpcibase = PHYSIO+0x41800, /* for registers below */ + AddrMpp = PHYSIO+0x10000, + AddrSdio = PHYSIO+0x90000, +}; + +enum { + Socrevz0, + Socreva0 = 2, + Socreva1, +}; + +enum { + /* registers; if we actually use these, change to soc.pci(base)->reg */ + PciBAR0 = Addrpcibase + 4, /* base address */ + PciBAR1 = Addrpcibase + 8, + + PciCP = Addrpci + 0x64, /* capabilities pointer */ + + PciINTL = Addrpci + 0x3c, /* interrupt line */ + PciINTP = PciINTL + 1, /* interrupt pin */ +}; + +/* + * interrupt stuff + */ + +enum { + Irqlo, Irqhi, Irqbridge, +}; + +enum { + /* main interrupt cause low register bit #s (LE) */ + IRQ0hisum, /* summary of main intr high cause reg */ + IRQ0bridge, + IRQ0h2cdoorbell, + IRQ0c2hdoorbell, + _IRQ0reserved0, + IRQ0xor0chan0, + IRQ0xor0chan1, + IRQ0xor1chan0, + IRQ0xor1chan1, + IRQ0pex0int, /* pex = pci-express */ + _IRQ0reserved1, + IRQ0gbe0sum, + IRQ0gbe0rx, + IRQ0gbe0tx, + IRQ0gbe0misc, + IRQ0gbe1sum, + IRQ0gbe1rx, + IRQ0gbe1tx, + IRQ0gbe1misc, + IRQ0usb0, + _IRQ0reserved2, + IRQ0sata, + IRQ0crypto, + IRQ0spi, + IRQ0audio, + _IRQ0reserved3, + IRQ0ts0, + _IRQ0reserved4, + IRQ0sdio, + IRQ0twsi, + IRQ0avb, + IRQ0tdm, + + /* main interrupt cause high register bit #s (LE) */ + _IRQ1reserved0 = 0, + IRQ1uart0, + IRQ1uart1, + IRQ1gpiolo0, + IRQ1gpiolo1, + IRQ1gpiolo2, + IRQ1gpiolo3, + IRQ1gpiohi0, + IRQ1gpiohi1, + IRQ1gpiohi2, + IRQ1gpiohi3, + IRQ1xor0err, + IRQ1xor1err, + IRQ1pex0err, + _IRQ1reserved1, + IRQ1gbe0err, + IRQ1gbe1err, + IRQ1usberr, + IRQ1cryptoerr, + IRQ1audioerr, + _IRQ1reserved2, + _IRQ1reserved3, + IRQ1rtc, + + /* bridged-interrupt causes */ + IRQcpuself = 0, + IRQcputimer0, + IRQcputimer1, + IRQcputimerwd, + IRQaccesserr, +}; + +/* + * interrupt controller + */ +typedef struct IntrReg IntrReg; +struct IntrReg +{ + struct { + ulong irq; /* main intr cause reg (ro) */ + ulong irqmask; + ulong fiqmask; + ulong epmask; + } lo, hi; +}; + +/* + * CPU control & status (archkw.c and trap.c) + */ +typedef struct CpucsReg CpucsReg; +struct CpucsReg +{ + ulong cpucfg; + ulong cpucsr; + ulong rstout; + ulong softreset; + ulong irq; /* mbus(-l) bridge interrupt cause */ + ulong irqmask; /* ⋯ mask */ + ulong mempm; /* memory power mgmt. control */ + ulong clockgate; /* clock enable bits */ + ulong biu; + ulong pad0; + ulong l2cfg; /* turn l2 cache on or off, set coherency */ + ulong pad1[2]; + ulong l2tm0; + ulong l2tm1; + ulong pad2[2]; + ulong l2pm; + ulong ram0; + ulong ram1; + ulong ram2; + ulong ram3; +}; + +enum { + /* cpucfg bits */ + Cfgvecinithi = 1<<1, /* boot at 0xffff0000, not 0; default 1 */ + Cfgbigendreset = 3<<1, /* init. as big-endian at reset; default 0 */ + Cfgiprefetch = 1<<16, /* instruction prefetch enable */ + Cfgdprefetch = 1<<17, /* data prefetch enable */ + + /* cpucsr bits */ + Reset = 1<<1, /* reset cpu core */ + + /* rstout bits */ + RstoutPex = 1<<0, /* assert RSTOUTn at pci-e reset */ + RstoutWatchdog = 1<<1, /* assert RSTOUTn at watchdog timeout */ + RstoutSoft = 1<<2, /* assert RSTOUTn at sw reset */ + + /* softreset bits */ + ResetSystem = 1<<0, /* assert RSTOUTn pin on SoftRstOutEn */ + + /* l2cfg bits */ + L2ecc = 1<<2, + L2exists = 1<<3, /* l2 cache doesn't ignore cpu */ + L2writethru = 1<<4, /* always WT, else see PTE C & B */ +}; + +enum { + /* from 88f6281 func'l specs (MV-S104860-00), tables 2 & 3, chapter 2 */ + Targdram = 0, /* ddr sdram */ + Targflash = 1, + Targcesasram = 3, /* security accelerator sram */ + + /* attributes */ + Attrcs0 = 0xe, /* chip select 0 (low dram) */ + Attrcs1 = 0xd, /* chip select 1 (high dram) */ + Attrbootrom = 0x1d, + Attrspi = 0x1e, + Attrnand = 0x2f, + + Winenable = 1<<0, +}; + +typedef struct Pciex Pciex; +struct Pciex { + ushort venid; /* 0x11ab means Marvell */ + ushort devid; /* 0x6281 means 6281 */ + ulong csr; + ulong revid; + ulong bistcache; /* bist hdr type & cache-line size */ + ulong bar0; + ulong bar0hi; + ulong bar1; + ulong bar1hi; + ulong bar2; + ulong bar2hi; + ulong _pad0; + ushort ssvenid; /* 0x11ab means Marvell */ + ushort ssdevid; /* 0x11ab means Marvell */ + ulong rombar; + ulong caplist; + ulong _pad1; + ulong intrpinline; /* interrupt pin & line */ + ulong pmcap; /* power mgmt. capability header */ + ulong pmcsr; /* power mgmt. control & status */ + ulong _pad2[2]; + ulong msictl; /* msi message control */ + ulong msiaddr; + ulong msiaddrhi; + ulong msidata; + ulong cap; + ulong devcap; + ulong devcsr; + ulong linkcap; + ulong linkcsr; + + uchar _pad[0x40100-0x40074]; + ulong errrep; /* advanced error report header */ + ulong uncorrerr; /* uncorrectable error status */ + ulong uncorrerrmask; /* uncorrectable error mask */ + ulong uncorrerrsev; /* uncorrectable error severity */ + ulong correrr; /* correctable error status */ + ulong correrrmask; /* correctable error mask */ + ulong errcap; /* advanced error capability & ctl. */ + ulong hdrlog[4]; /* header log */ + /* continues with more rubbish at 0x41a00. some day... */ +}; diff --git a/sys/src/9/kw/l.s b/sys/src/9/kw/l.s new file mode 100755 index 000000000..f538b5d8d --- /dev/null +++ b/sys/src/9/kw/l.s @@ -0,0 +1,774 @@ +/* + * sheevaplug machine assist + * arm926ej-s processor at 1.2GHz + * + * loader uses R11 as scratch. + * R9 and R10 are used for `extern register' variables. + * + * ARM v7 arch. ref. man. (I know, this is v5) §B1.3.3 that + * we don't need barriers around moves to CPSR. The ARM v6 manual + * seems to be silent on the subject. + */ +#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 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 */ +_main: + /* SVC mode, interrupts disabled */ + MOVW $(PsrDirq|PsrDfiq|PsrMsvc), R1 + MOVW R1, CPSR + BARRIERS + + /* + * disable the MMU & caches, + * switch to system permission & 32-bit addresses. + */ + MOVW $(CpCsystem|CpCd32|CpCi32), R1 + MCR CpSC, 0, R1, C(CpCONTROL), C(0) + ISB + + /* + * disable the Sheevaplug's L2 cache, invalidate all caches + */ + + /* flush caches. 926ejs manual says we have to do it iteratively. */ +_dwbinv0: + MRC CpSC, 0, PC, C(CpCACHE), C(CpCACHEwbi), CpCACHEtest + BNE _dwbinv0 + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + /* make the l2 cache pay attention */ + MOVW $(PHYSIO+0x20100), R1 /* CPUCSREG */ + MOVW (4*10)(R1), R2 + ORR $(1<<3), R2 /* cpu->l2cfg |= L2exists */ + MOVW R2, (4*10)(R1) + ISB + + /* invalidate l2 cache */ + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2inv), CpTCl2all + ISB + + /* disable l2 cache. do this while l1 caches are off */ + MRC CpSC, CpL2, R1, C(CpTESTCFG), C(CpTCl2cfg), CpTCl2conf + /* disabling write allocation is probably for cortex-a8 errata 460075 */ + /* l2 off, no wr alloc, no streaming */ + BIC $(CpTCl2ena | CpTCl2wralloc | CpTCldcstream), R1 + MCR CpSC, CpL2, R1, C(CpTESTCFG), C(CpTCl2cfg), CpTCl2conf + BARRIERS + + /* flush caches. 926ejs manual says we have to do it iteratively. */ +_dwbinv1: + MRC CpSC, 0, PC, C(CpCACHE), C(CpCACHEwbi), CpCACHEtest + BNE _dwbinv1 + BARRIERS + +WAVE('\r') + /* clear Mach */ + MOVW $PADDR(MACHADDR), R4 /* address of Mach */ +_machZ: + MOVW R0, (R4) + ADD $4, R4 /* bump PTE address */ + CMP.S $PADDR(L1+L1X(0)), R4 + BNE _machZ + + /* + * set up the MMU page table + */ + + /* clear all PTEs first, to provide a default */ +WAVE('\n') + MOVW $PADDR(L1+L1X(0)), R4 /* address of PTE for 0 */ +_ptenv0: + ZEROPTE() + CMP.S $PADDR(L1+16*KiB), R4 + BNE _ptenv0 + + /* double map of PHYSDRAM, KZERO to PHYSDRAM for first few MBs */ + MOVW $PTEDRAM, R2 /* PTE bits */ + MOVW $PHYSDRAM, R3 /* pa */ + MOVW $PADDR(L1+L1X(PHYSDRAM)), R4 /* address of PTE for PHYSDRAM */ + MOVW $16, R5 +_ptdbl: + FILLPTE() + SUB.S $1, R5 + BNE _ptdbl + + /* + * back up and fill in PTEs for memory at KZERO + * there is 1 bank of 512MB of SDRAM at PHYSDRAM + */ + 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 */ +_ptekrw: /* set PTEs for 512MiB */ + FILLPTE() + SUB.S $1, R5 + BNE _ptekrw + + /* + * back up and fill in PTE for MMIO + */ + MOVW $PTEIO, R2 /* PTE bits */ + MOVW $PHYSIO, R3 + MOVW $PADDR(L1+L1X(VIRTIO)), R4 /* start with PTE for VIRTIO */ + FILLPTE() + + /* mmu.c sets up the vectors later */ + + /* + * set up a temporary stack; avoid data & bss segments + */ + MOVW $(PHYSDRAM | (128*1024*1024)), R13 + +WAVE('P') + /* set the domain access control */ + MOVW $Client, R0 + BL dacput(SB) + + /* set the translation table base */ + MOVW $PADDR(L1), R0 + BL ttbput(SB) + + MOVW $0, R0 + BL pidput(SB) /* paranoia */ + + /* the little dance to turn the MMU & caches on */ +WAVE('l') + BL cacheuwbinv(SB) + BL mmuinvalidate(SB) + BL mmuenable(SB) + +WAVE('a') + /* 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('n') + /* undo double map of 0, KZERO */ + MOVW $PADDR(L1+L1X(0)), R4 /* address of PTE for 0 */ + MOVW $0, R0 + MOVW $16, R5 +_ptudbl: + MOVW R0, (R4) + ADD $4, R4 /* bump PTE address */ + ADD $MiB, R0 /* bump pa */ + SUB.S $1, R5 + BNE _ptudbl + BARRIERS + MCR CpSC, 0, R0, C(CpTLB), C(CpTLBinvd), CpTLBinvse + MCR CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv + BARRIERS + +WAVE(' ') + /* pass Mach to main and set up the stack */ + MOVW $(MACHADDR), R0 /* Mach */ + MOVW R0, R13 + ADD $(MACHSIZE), R13 /* stack pointer */ + SUB $4, R13 /* space for link register */ + + BL main(SB) /* void main(Mach*) */ + /* fall through */ + + +/* not used */ +TEXT _reset(SB), 1, $-4 + /* turn the caches off */ + MOVW $(PsrDirq|PsrDfiq|PsrMsvc), R0 + MOVW R0, CPSR + BARRIERS + BL cacheuwbinv(SB) + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + BIC $(CpCwb|CpCicache|CpCdcache|CpCalign), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS +WAVE('R') + + /* redo double map of 0, KZERO */ + MOVW $(L1+L1X(0)), R4 /* address of PTE for 0 */ + MOVW $PTEDRAM, R2 /* PTE bits */ + MOVW $0, R3 + MOVW $16, 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 + + BARRIERS +WAVE('e') + MOVW $0, R0 + MCR CpSC, 0, R0, C(CpTLB), C(CpTLBinvd), CpTLBinv + MCR CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv + BARRIERS + + /* back to 29- or 26-bit addressing, mainly for SB */ + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + BIC $(CpCd32|CpCi32), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS + + /* turn the MMU off */ + MOVW $PHYSDRAM, R0 + BL _r15warp(SB) + BL mmuinvalidate(SB) + BL mmudisable(SB) + +WAVE('s') + /* set new reset vector */ + MOVW $0, R2 + MOVW $0xe59ff018, R3 /* MOVW 0x18(R15), R15 */ + MOVW R3, (R2) +WAVE('e') + + MOVW $PHYSBOOTROM, R3 + MOVW R3, 0x20(R2) /* where $0xe59ff018 jumps to */ + BARRIERS +WAVE('t') +WAVE('\r') +WAVE('\n') + + /* ...and jump to it */ + MOVW R2, R15 /* software reboot */ +_limbo: /* should not get here... */ + B _limbo /* ... and can't get out */ + BL _div(SB) /* hack to load _div, etc. */ + +TEXT _r15warp(SB), 1, $-4 + BIC $KSEGM, R14 + ORR R0, R14 + BIC $KSEGM, R13 + ORR R0, R13 + RET + +/* clobbers R1, R6 */ +TEXT myputc(SB), 1, $-4 + MOVW $PHYSCONS, R6 +_busy: + MOVW 20(R6), R1 + BIC.S $~(1<<5), R1 /* (x->lsr & LSRthre) == 0? */ + BEQ _busy + MOVW R3, (R6) /* print */ + ISB + RET + +/* + * l1 caches + */ + +TEXT l1cacheson(SB), 1, $-4 + MOVW CPSR, R5 + ORR $(PsrDirq|PsrDfiq), R5, R4 + MOVW R4, CPSR /* splhi */ + + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + ORR $(CpCdcache|CpCicache|CpCwb), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS + + MOVW R5, CPSR /* splx */ + RET + +TEXT l1cachesoff(SB), 1, $-4 + MOVM.DB.W [R14], (SP) /* save lr on stack */ + + MOVW CPSR, R5 + ORR $(PsrDirq|PsrDfiq), R5, R4 + MOVW R4, CPSR /* splhi */ + + BL cacheuwbinv(SB) + + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + BIC $(CpCdcache|CpCicache|CpCwb), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS + + MOVW R5, CPSR /* splx */ + MOVM.IA.W (SP), [R14] /* restore lr */ + RET + +/* + * cache* functions affect only the L1 caches, which are VIVT. + */ + +TEXT cachedwb(SB), 1, $-4 /* D writeback */ + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + BARRIERS /* force outstanding stores to cache */ + /* keep writing back dirty cache lines until no more exist */ +_dwb: + MRC CpSC, 0, PC, C(CpCACHE), C(CpCACHEwb), CpCACHEtest + BNE _dwb + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + MOVW R3, CPSR /* splx */ + RET + +TEXT cachedwbse(SB), 1, $-4 /* D writeback SE */ + MOVW R0, R2 /* first arg: address */ + + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + BARRIERS /* force outstanding stores to cache */ + MOVW 4(FP), R1 /* second arg: size */ + +// CMP.S $(4*1024), R1 +// BGT _dwb + ADD R2, R1 + BIC $(CACHELINESZ-1), R2 +_dwbse: + MCR CpSC, 0, R2, C(CpCACHE), C(CpCACHEwb), CpCACHEse + ADD $CACHELINESZ, R2 + CMP.S R2, R1 + BGT _dwbse + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + MOVW R3, CPSR /* splx */ + RET + +TEXT cachedwbinv(SB), 1, $-4 /* D writeback+invalidate */ + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + BARRIERS /* force outstanding stores to cache */ + /* keep writing back dirty cache lines until no more exist */ +_dwbinv: + MRC CpSC, 0, PC, C(CpCACHE), C(CpCACHEwbi), CpCACHEtest + BNE _dwbinv + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + MOVW R3, CPSR /* splx */ + RET + +TEXT cachedwbinvse(SB), 1, $-4 /* D writeback+invalidate SE */ + MOVW R0, R2 /* first arg: address */ + + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + BARRIERS /* force outstanding stores to cache */ + MOVW 4(FP), R1 /* second arg: size */ + + DSB +// CMP.S $(4*1024), R1 +// BGT _dwbinv + ADD R2, R1 + BIC $(CACHELINESZ-1), R2 +_dwbinvse: + MCR CpSC, 0, R2, C(CpCACHE), C(CpCACHEwbi), CpCACHEse + ADD $CACHELINESZ, R2 + CMP.S R2, R1 + BGT _dwbinvse + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + MOVW R3, CPSR /* splx */ + RET + +TEXT cachedinvse(SB), 1, $-4 /* D invalidate SE */ + MOVW R0, R2 /* first arg: address */ + + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + MOVW 4(FP), R1 /* second arg: size */ + + DSB +// CMP.S $(4*1024), R1 +// BGT _dinv + ADD R2, R1 + BIC $(CACHELINESZ-1), R2 +_dinvse: + MCR CpSC, 0, R2, C(CpCACHE), C(CpCACHEinvd), CpCACHEse + ADD $CACHELINESZ, R2 + CMP.S R2, R1 + BGT _dinvse + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + MOVW R3, CPSR /* splx */ + RET + +TEXT cacheuwbinv(SB), 1, $-4 /* D+I writeback+invalidate */ + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + BARRIERS /* force outstanding stores to cache */ + /* keep writing back dirty cache lines until no more exist */ +_uwbinv: /* D writeback+invalidate */ + MRC CpSC, 0, PC, C(CpCACHE), C(CpCACHEwbi), CpCACHEtest + BNE _uwbinv + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + MOVW $0, R0 /* I invalidate */ + MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + MOVW R3, CPSR /* splx */ + RET + +TEXT cacheiinv(SB), 1, $-4 /* I invalidate */ + BARRIERS + MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + RET + +TEXT cachedinv(SB), 1, $-4 /* D invalidate */ +_dinv: + BARRIERS + MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvd), CpCACHEall + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + RET + +/* + * l2 cache + * + * these functions assume that the necessary l1 cache operations have been + * or will be done explicitly by the caller. + */ + +/* enable l2 cache in config coproc. reg. do this while l1 caches are off. */ +TEXT l2cachecfgon(SB), 1, $-4 + BARRIERS + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2inv), CpTCl2all + BARRIERS + + MRC CpSC, CpL2, R1, C(CpTESTCFG), C(CpTCl2cfg), CpTCl2conf + ORR $(CpTCl2ena | CpTCl2prefdis), R1 /* l2 on, prefetch off */ + MCR CpSC, CpL2, R1, C(CpTESTCFG), C(CpTCl2cfg), CpTCl2conf + BARRIERS + RET + +/* disable l2 cache in config coproc. reg. do this while l1 caches are off. */ +TEXT l2cachecfgoff(SB), 1, $-4 + BARRIERS + MRC CpSC, CpL2, R1, C(CpTESTCFG), C(CpTCl2cfg), CpTCl2conf + BIC $CpTCl2ena, R1 + MCR CpSC, CpL2, R1, C(CpTESTCFG), C(CpTCl2cfg), CpTCl2conf + BARRIERS + + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2inv), CpTCl2all + BARRIERS + RET + +TEXT l2cacheuwb(SB), 1, $-4 /* L2 unified writeback */ + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2flush), CpTCl2all + ISB + RET + +TEXT l2cacheuwbse(SB), 1, $-4 /* L2 unified writeback SE */ + MOVW R0, R2 /* first arg: address */ + + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + MOVW 4(FP), R1 /* second arg: size */ + + ADD R2, R1 + BIC $(CACHELINESZ-1), R2 +_l2wbse: + MCR CpSC, CpL2, R2, C(CpTESTCFG), C(CpTCl2flush), CpTCl2seva + ADD $CACHELINESZ, R2 + CMP.S R2, R1 + BGT _l2wbse + ISB + + MOVW R3, CPSR /* splx */ + RET + +TEXT l2cacheuwbinv(SB), 1, $-4 /* L2 unified writeback+invalidate */ + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2flush), CpTCl2all + ISB + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2inv), CpTCl2all + ISB + + MOVW R3, CPSR /* splx */ + RET + +TEXT l2cacheuwbinvse(SB), 1, $-4 /* L2 unified writeback+invalidate SE */ + MOVW R0, R2 /* first arg: address */ + + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + MOVW 4(FP), R1 /* second arg: size */ + + ADD R2, R1 + BIC $(CACHELINESZ-1), R2 +_l2wbinvse: + MCR CpSC, CpL2, R2, C(CpTESTCFG), C(CpTCl2flush), CpTCl2seva + ISB + MCR CpSC, CpL2, R2, C(CpTESTCFG), C(CpTCl2inv), CpTCl2seva + ADD $CACHELINESZ, R2 + CMP.S R2, R1 + BGT _l2wbinvse + ISB + + MOVW R3, CPSR /* splx */ + RET + +TEXT l2cacheuinv(SB), 1, $-4 /* L2 unified invalidate */ + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2inv), CpTCl2all + ISB + RET + +TEXT l2cacheuinvse(SB), 1, $-4 /* L2 unified invalidate SE */ + MOVW R0, R2 /* first arg: address */ + + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + + MOVW 4(FP), R1 /* second arg: size */ + + ADD R2, R1 + BIC $(CACHELINESZ-1), R2 +_l2invse: + MCR CpSC, CpL2, R2, C(CpTESTCFG), C(CpTCl2inv), CpTCl2seva + ADD $CACHELINESZ, R2 + CMP.S R2, R1 + BGT _l2invse + ISB + + MOVW R3, CPSR /* splx */ + RET + +/* + * enable mmu, i and d caches, and high vector + */ +TEXT mmuenable(SB), 1, $-4 + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + ORR $(CpChv|CpCmmu|CpCdcache|CpCicache|CpCwb|CpCsystem), R0 + BIC $(CpCrom), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS + RET + +TEXT mmudisable(SB), 1, $-4 + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + BIC $(CpChv|CpCmmu|CpCdcache|CpCicache|CpCwb), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS + RET + +TEXT mmuinvalidate(SB), 1, $-4 /* invalidate all */ + MOVW $0, R0 + MCR CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv + 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) + RET + +TEXT ttbget(SB), 1, $-4 /* translation table base */ + MRC CpSC, 0, R0, C(CpTTB), C(0) + RET + +TEXT ttbput(SB), 1, $-4 /* translation table base */ + MCR CpSC, 0, R0, C(CpTTB), C(0) + ISB + 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) + ISB + 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 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) + ISB + RET + +TEXT splhi(SB), 1, $-4 + MOVW $(MACHADDR+4), R2 /* save caller pc in Mach */ + MOVW R14, 0(R2) + + MOVW CPSR, R0 /* turn off interrupts */ + ORR $(PsrDirq), R0, R1 + MOVW R1, CPSR + RET + +TEXT spllo(SB), 1, $-4 + MOVW CPSR, R0 + BIC $(PsrDirq), R0, R1 + MOVW R1, CPSR + RET + +TEXT splx(SB), 1, $-4 + MOVW $(MACHADDR+0x04), R2 /* save caller pc in Mach */ + MOVW R14, 0(R2) + + MOVW R0, R1 /* reset interrupt level */ + MOVW CPSR, R0 + MOVW R1, CPSR + RET + +TEXT splxpc(SB), 1, $-4 /* for iunlock */ + MOVW R0, R1 + MOVW CPSR, R0 + MOVW R1, CPSR + RET + +TEXT spldone(SB), 1, $0 + RET + +TEXT islo(SB), 1, $-4 + MOVW CPSR, R0 + AND $(PsrDirq), R0 + EOR $(PsrDirq), R0 + RET + +TEXT splfhi(SB), $-4 + MOVW CPSR, R0 + ORR $(PsrDfiq|PsrDirq), R0, R1 + MOVW R1, CPSR + RET + +//TEXT splflo(SB), $-4 +// MOVW CPSR, R0 +// BIC $(PsrDfiq), R0, R1 +// MOVW R1, CPSR +// RET + +TEXT tas(SB), $-4 +TEXT _tas(SB), $-4 + MOVW R0,R1 + MOVW $1,R0 + SWPW R0,(R1) /* fix: deprecated in armv7 */ + RET + +//TEXT tas32(SB), 1, $-4 +// MOVW R0, R1 +// MOVW $0xDEADDEAD, R0 +// MOVW R0, R3 +// SWPW R0, (R1) +// CMP.S R0, R3 +// BEQ _tasout +// EOR R3, R3 /* R3 = 0 */ +// CMP.S R0, R3 +// BEQ _tasout +// MOVW $1, R15 /* abort: lock != 0 && lock != $0xDEADDEAD */ +//_tasout: +// 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 */ + BARRIERS + MOVW $0, R0 + RET + +TEXT gotolabel(SB), 1, $-4 + MOVW 0(R0), R13 /* sp */ + MOVW 4(R0), R14 /* pc */ + BARRIERS + MOVW $1, R0 + RET + +TEXT getcallerpc(SB), 1, $-4 + MOVW 0(R13), R0 + RET + +TEXT _idlehands(SB), 1, $-4 + MOVW CPSR, R3 +// ORR $PsrDirq, R3, R1 /* splhi */ + BIC $PsrDirq, R3, R1 /* spllo */ + MOVW R1, CPSR + + MOVW $0, R0 /* wait for interrupt */ + MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEintr), CpCACHEwait + ISB + + MOVW R3, CPSR /* splx */ + RET + +TEXT barriers(SB), 1, $-4 + BARRIERS + RET diff --git a/sys/src/9/kw/lexception.s b/sys/src/9/kw/lexception.s new file mode 100755 index 000000000..e3f330653 --- /dev/null +++ b/sys/src/9/kw/lexception.s @@ -0,0 +1,187 @@ +/* + * arm exception handlers + */ +#include "arm.s" + +#undef B /* B is for 'botch' */ + +/* + * exception vectors, copied by trapinit() to somewhere useful + */ +TEXT vectors(SB), 1, $-4 + MOVW 0x18(R15), R15 /* reset */ + MOVW 0x18(R15), R15 /* undefined instr. */ + MOVW 0x18(R15), R15 /* SWI & SMC */ + MOVW 0x18(R15), R15 /* prefetch abort */ + MOVW 0x18(R15), R15 /* data abort */ + MOVW 0x18(R15), R15 /* reserved */ + MOVW 0x18(R15), R15 /* IRQ */ + MOVW 0x18(R15), R15 /* FIQ */ + +TEXT vtable(SB), 1, $-4 + WORD $_vsvc(SB) /* reset, in svc mode already */ + WORD $_vund(SB) /* undefined, switch to svc mode */ + WORD $_vsvc(SB) /* swi, in svc mode already */ + WORD $_vpabt(SB) /* prefetch abort, switch to svc mode */ + WORD $_vdabt(SB) /* data abort, switch to svc mode */ + WORD $_vsvc(SB) /* reserved */ + WORD $_virq(SB) /* IRQ, switch to svc mode */ +// WORD $_vfiq(SB) /* FIQ, switch to svc mode */ + WORD $_virq(SB) /* FIQ, switch to svc mode */ + +TEXT _vrst(SB), 1, $-4 + BL _reset(SB) + +TEXT _vsvc(SB), 1, $-4 /* SWI */ + MOVW.W R14, -4(R13) /* ureg->pc = interrupted PC */ + MOVW SPSR, R14 /* ureg->psr = SPSR */ + MOVW.W R14, -4(R13) /* ... */ + MOVW $PsrMsvc, R14 /* ureg->type = PsrMsvc */ + MOVW.W R14, -4(R13) /* ... */ + + /* avoid the ambiguity described in notes/movm.w. */ +// MOVM.DB.W.S [R0-R14], (R13) /* save user level registers, at end r13 points to ureg */ + MOVM.DB.S [R0-R14], (R13) /* save user level registers */ + SUB $(15*4), R13 /* r13 now points to ureg */ + + MOVW $setR12(SB), R12 /* Make sure we've got the kernel's SB loaded */ + +// MOVW $(KSEG0+16*KiB-MACHSIZE), R10 /* m */ + MOVW $(L1-MACHSIZE), R10 /* m */ + MOVW 8(R10), R9 /* up */ + + MOVW R13, R0 /* first arg is pointer to ureg */ + SUB $8, R13 /* space for argument+link */ + + BL syscall(SB) + + ADD $(8+4*15), R13 /* make r13 point to ureg->type */ + MOVW 8(R13), R14 /* restore link */ + MOVW 4(R13), R0 /* restore SPSR */ + MOVW R0, SPSR /* ... */ + MOVM.DB.S (R13), [R0-R14] /* restore registers */ + ADD $8, R13 /* pop past ureg->{type+psr} */ + RFE /* MOVM.IA.S.W (R13), [R15] */ + +TEXT _vund(SB), 1, $-4 /* undefined */ + MOVM.IA [R0-R4], (R13) /* free some working space */ + MOVW $PsrMund, R0 + B _vswitch + +TEXT _vpabt(SB), 1, $-4 /* prefetch abort */ + MOVM.IA [R0-R4], (R13) /* free some working space */ + MOVW $PsrMabt, R0 /* r0 = type */ + B _vswitch + +TEXT _vdabt(SB), 1, $-4 /* data abort */ + MOVM.IA [R0-R4], (R13) /* free some working space */ + MOVW $(PsrMabt+1), R0 /* r0 = type */ + B _vswitch + +TEXT _virq(SB), 1, $-4 /* IRQ */ + MOVM.IA [R0-R4], (R13) /* free some working space */ + MOVW $PsrMirq, R0 /* r0 = type */ + B _vswitch + + /* + * come here with type in R0 and R13 pointing above saved [r0-r4]. + * we'll switch to SVC mode and then call trap. + */ +_vswitch: + MOVW SPSR, R1 /* save SPSR for ureg */ + MOVW R14, R2 /* save interrupted pc for ureg */ + MOVW R13, R3 /* save pointer to where the original [R0-R4] are */ + + /* + * switch processor to svc mode. this switches the banked registers + * (r13 [sp] and r14 [link]) to those of svc mode. + */ + MOVW CPSR, R14 + BIC $PsrMask, R14 + ORR $(PsrDirq|PsrDfiq|PsrMsvc), R14 + MOVW R14, CPSR /* switch! */ + + AND.S $0xf, R1, R4 /* interrupted code kernel or user? */ + BEQ _userexcep + + /* here for trap from SVC mode */ + MOVM.DB.W [R0-R2], (R13) /* set ureg->{type, psr, pc}; r13 points to ureg->type */ + MOVM.IA (R3), [R0-R4] /* restore [R0-R4] from previous mode's stack */ + + /* + * avoid the ambiguity described in notes/movm.w. + * In order to get a predictable value in R13 after the stores, + * separate the store-multiple from the stack-pointer adjustment. + * We'll assume that the old value of R13 should be stored on the stack. + */ + /* save kernel level registers, at end r13 points to ureg */ +// MOVM.DB.W [R0-R14], (R13) + MOVM.DB [R0-R14], (R13) + SUB $(15*4), R13 /* SP now points to saved R0 */ + + MOVW $setR12(SB), R12 /* Make sure we've got the kernel's SB loaded */ + + MOVW R13, R0 /* first arg is pointer to ureg */ + SUB $(4*2), R13 /* space for argument+link (for debugger) */ + MOVW $0xdeaddead, R11 /* marker */ + + BL trap(SB) + + ADD $(4*2+4*15), R13 /* make r13 point to ureg->type */ + MOVW 8(R13), R14 /* restore link */ + MOVW 4(R13), R0 /* restore SPSR */ + MOVW R0, SPSR /* ... */ + + MOVM.DB (R13), [R0-R14] /* restore registers */ + + ADD $(4*2), R13 /* pop past ureg->{type+psr} to pc */ + RFE /* MOVM.IA.S.W (R13), [R15] */ + + /* here for trap from USER mode */ +_userexcep: + MOVM.DB.W [R0-R2], (R13) /* set ureg->{type, psr, pc}; r13 points to ureg->type */ + MOVM.IA (R3), [R0-R4] /* restore [R0-R4] from previous mode's stack */ + + /* avoid the ambiguity described in notes/movm.w. */ +// MOVM.DB.W.S [R0-R14], (R13) /* save kernel level registers, at end r13 points to ureg */ + MOVM.DB.S [R0-R14], (R13) /* save kernel level registers */ + SUB $(15*4), R13 /* r13 now points to ureg */ + + MOVW $setR12(SB), R12 /* Make sure we've got the kernel's SB loaded */ + +// MOVW $(KSEG0+16*KiB-MACHSIZE), R10 /* m */ + MOVW $(L1-MACHSIZE), R10 /* m */ + MOVW 8(R10), R9 /* up */ + + MOVW R13, R0 /* first arg is pointer to ureg */ + SUB $(4*2), R13 /* space for argument+link (for debugger) */ + + BL trap(SB) + + ADD $(4*2+4*15), R13 /* make r13 point to ureg->type */ + MOVW 8(R13), R14 /* restore link */ + MOVW 4(R13), R0 /* restore SPSR */ + MOVW R0, SPSR /* ... */ + MOVM.DB.S (R13), [R0-R14] /* restore registers */ + ADD $(4*2), R13 /* pop past ureg->{type+psr} */ + RFE /* MOVM.IA.S.W (R13), [R15] */ + +TEXT _vfiq(SB), 1, $-4 /* FIQ */ + RFE /* FIQ is special, ignore it for now */ + +/* + * set the stack value for the mode passed in R0 + */ +TEXT setr13(SB), 1, $-4 + MOVW 4(FP), R1 + + MOVW CPSR, R2 + BIC $PsrMask, R2, R3 + ORR R0, R3 + MOVW R3, CPSR /* switch to new mode */ + + MOVW R13, R0 /* return old sp */ + MOVW R1, R13 /* install new one */ + + MOVW R2, CPSR /* switch back to old mode */ + RET diff --git a/sys/src/9/kw/lproc.s b/sys/src/9/kw/lproc.s new file mode 100755 index 000000000..0b4eac238 --- /dev/null +++ b/sys/src/9/kw/lproc.s @@ -0,0 +1,47 @@ +#include "mem.h" +#include "arm.h" + +/* + * This is the first jump from kernel to user mode. + * Fake a return from interrupt. + * + * Enter with R0 containing the user stack pointer. + * UTZERO + 0x20 is always the entry point. + * + */ +TEXT touser(SB), 1, $-4 + /* store the user stack pointer into the USR_r13 */ + MOVM.DB.W [R0], (R13) + /* avoid the ambiguity described in notes/movm.w. */ +// MOVM.S.IA.W (R13), [R13] + MOVM.S (R13), [R13] + ADD $4, R13 + + /* set up a PSR for user level */ + MOVW $(PsrMusr), R0 + MOVW R0, SPSR + + /* save the PC on the stack */ + MOVW $(UTZERO+0x20), R0 + MOVM.DB.W [R0], (R13) + + /* + * note that 5a's RFE is not the v6 arch. instruction (0xe89d0a00, + * I think), which loads CPSR from the word after the PC at (R13), + * but rather the pre-v6 simulation `MOVM.IA.S.W (R13), [R15]' + * (0xe8fd8000 since MOVM is LDM in this case), which loads CPSR + * not from memory but from SPSR due to `.S'. + */ + RFE + +/* + * here to jump to a newly forked process + */ +TEXT forkret(SB), 1, $-4 + ADD $(4*15), R13 /* make r13 point to ureg->type */ + MOVW 8(R13), R14 /* restore link */ + MOVW 4(R13), R0 /* restore SPSR */ + MOVW R0, SPSR /* ... */ + MOVM.DB.S (R13), [R0-R14] /* restore registers */ + ADD $8, R13 /* pop past ureg->{type+psr} */ + RFE /* MOVM.IA.S.W (R13), [R15] */ diff --git a/sys/src/9/kw/main.c b/sys/src/9/kw/main.c new file mode 100755 index 000000000..2fa13917d --- /dev/null +++ b/sys/src/9/kw/main.c @@ -0,0 +1,678 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" + +#include "init.h" +#include "arm.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; +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; + +#ifdef CRYPTOSANDBOX +uchar sandbox[64*1024+BY2PG]; +#endif + +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; + + 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; +} + +#include "io.h" + +typedef struct Spiregs Spiregs; +struct Spiregs { + ulong ictl; /* interface ctl */ + ulong icfg; /* interface config */ + ulong out; /* data out */ + ulong in; /* data in */ + ulong ic; /* interrupt cause */ + ulong im; /* interrupt mask */ + ulong _pad[2]; + ulong dwrcfg; /* direct write config */ + ulong dwrhdr; /* direct write header */ +}; + +enum { + /* ictl bits */ + Csnact = 1<<0, /* serial memory activated */ + + /* icfg bits */ + Bytelen = 1<<5, /* 2^(this_bit) bytes per transfer */ + Dirrdcmd= 1<<10, /* flag: fast read */ +}; + +static void +dumpbytes(uchar *bp, long max) +{ + iprint("%#p: ", bp); + for (; max > 0; max--) + iprint("%02.2ux ", *bp++); + iprint("...\n"); +} + +static void +spiprobe(void) +{ + Spiregs *rp = (Spiregs *)soc.spi; + + rp->ictl |= Csnact; + coherence(); + rp->icfg |= Dirrdcmd | 3<<8; /* fast reads, 4-byte addresses */ + rp->icfg &= ~Bytelen; /* one-byte reads */ + coherence(); + +// print("spi flash at %#ux: memory reads enabled\n", PHYSSPIFLASH); +} + +void archconsole(void); + +/* + * entered from l.s with mmu enabled. + * + * we may have to realign the data segment; apparently 5l -H0 -R4096 + * does not pad the text segment. on the other hand, we may have been + * loaded by another kernel. + * + * be careful not to touch the data segment until we know it's aligned. + */ +void +main(Mach* mach) +{ + extern char bdata[], edata[], end[], etext[]; + static ulong vfy = 0xcafebabe; + + m = mach; + if (vfy != 0xcafebabe) + memmove(bdata, etext, edata - bdata); + if (vfy != 0xcafebabe) { + wave('?'); + panic("misaligned data segment"); + } + memset(edata, 0, end - edata); /* zero bss */ + vfy = 0; + +wave('9'); + machinit(); + archreset(); + mmuinit(); + + optionsinit("/boot/boot boot"); + quotefmtinstall(); + archconsole(); +wave(' '); + + /* want plan9.ini to be able to affect memory sizing in confinit */ + plan9iniinit(); /* before we step on plan9.ini in low memory */ + + confinit(); + /* xinit would print if it could */ + xinit(); + + /* + * Printinit will cause the first malloc call. + * (printinit->qopen->malloc) unless any of the + * above (like clockintr) 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(); + clockinit(); + + printinit(); + uartkirkwoodconsole(); + /* only now can we print */ + print("from Bell Labs\n\n"); + +#ifdef CRYPTOSANDBOX + print("sandbox: 64K at physical %#lux, mapped to 0xf10b0000\n", + PADDR((uintptr)sandbox & ~(BY2PG-1))); +#endif + + archconfinit(); + cpuidprint(); + timersinit(); + + procinit0(); + initseg(); + links(); + chandevreset(); /* most devices are discovered here */ + + pageinit(); + swapinit(); + userinit(); + schedinit(); + panic("schedinit returned"); +} + +void +cpuidprint(void) +{ + char name[64]; + + cputype2name(name, sizeof name); + print("cpu%d: %lldMHz ARM %s\n", m->machno, m->cpuhz/1000000, name); +} + +void +machinit(void) +{ + memset(m, 0, sizeof(Mach)); + 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(); +} + +/* + * 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); + + iprint("starting reboot..."); + writeconf(); + + shutdown(0); + + /* + * should be the only processor running now + */ + + print("shutting down...\n"); + delay(200); + + /* turn off buffered serial console */ + serialoq = nil; + + /* shutdown devices */ + chandevshutdown(); + + /* call off the dog */ + clockshutdown(); + + splhi(); + + /* setup reboot trampoline function */ + f = (void*)REBOOTADDR; + memmove(f, rebootcode, sizeof(rebootcode)); + cacheuwbinv(); + l2cacheuwb(); + + print("rebooting..."); + iprint("entry %#lux code %#lux size %ld\n", + PADDR(entry), PADDR(code), size); + delay(100); /* wait for uart to quiesce */ + + /* off we go - never to return */ + cacheuwbinv(); + l2cacheuwb(); + (*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]; + + assert(up != nil); + 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); + + chandevinit(); + + 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 + BY2PG - 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 + BY2PG - 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); + 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); + s->flushme++; + 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 sheevamem[] = { + /* + * Memory available to Plan 9: + * the 8K is reserved for ethernet dma access violations to scribble on. + */ + { .base = 0, .limit = 512*MB - 8*1024, }, +}; + +void +confinit(void) +{ + int i; + ulong kpages; + uintptr pa; + + /* + * Copy the physical memory configuration to Conf.mem. + */ + if(nelem(sheevamem) > nelem(conf.mem)){ + iprint("memory configuration botch\n"); + exit(1); + } + memmove(conf.mem, sheevamem, sizeof(sheevamem)); + + 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*90)/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; +} + +int +cmpswap(long *addr, long old, long new) +{ + return cas32(addr, old, new); +} diff --git a/sys/src/9/kw/mem.h b/sys/src/9/kw/mem.h new file mode 100755 index 000000000..b35e4dba7 --- /dev/null +++ b/sys/src/9/kw/mem.h @@ -0,0 +1,142 @@ +/* + * 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 BY2PG (4*KiB) /* bytes per page */ +#define PGSHIFT 12 /* log(BY2PG) */ +#define PGROUND(s) ROUNDUP(s, BY2PG) +#define ROUND(s, sz) (((s)+(sz-1))&~(sz-1)) + +#define MAXMACH 1 /* max # cpus system can run */ +#define MACHSIZE BY2PG + +#define KSTKSIZE (8*KiB) +#define STACKALIGN(sp) ((sp) & ~3) /* bug: assure with alloc */ + +/* + * Address spaces. + * KTZERO is used by kprof and dumpstack (if any). + * + * KZERO is mapped to physical 0. + * u-boot claims to take 0 - 8MB. + * + * vectors are at 0, plan9.ini is at KZERO+4K and is limited to 16K by + * devenv. L2 PTEs for trap vectors & i/o regs are stored from KZERO+56K + * to L1-MACHSIZE (KZERO+60K). cpu0's Mach struct is at L1 - MACHSIZE(4K) + * to L1 (KZERO+60K to KZERO+64K). L1 PTEs are stored from L1 to L1+32K + * (KZERO+64K to KZERO+96K). KTZERO may be anywhere after KZERO+96K. + */ + +#define KSEG0 0x60000000 /* kernel segment */ +/* mask to check segment; good for 512MB dram */ +#define KSEGM 0xE0000000 +#define KZERO KSEG0 /* kernel address space */ +#define CONFADDR (KZERO+4*KiB) /* unparsed plan9.ini */ +#define L1 (KZERO+64*KiB) /* tt ptes: 16KiB aligned */ +#define KTZERO (KZERO+0x800000) /* kernel text start */ + +#define UZERO 0 /* user segment */ +#define UTZERO (UZERO+BY2PG) /* user text start */ +#define USTKTOP KZERO /* 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) + +/* + * Time. + * Does this need to be here? Used in assembler? + */ +#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 CLOCKFREQ (200*1000*1000) /* TCLK on sheeva: 200MHz */ +//#define MS2TMR(t) ((ulong)(((uvlong)(t)*CLOCKFREQ)/1000)) +//#define US2TMR(t) ((ulong)(((uvlong)(t)*CLOCKFREQ)/1000000)) + +/* + * Legacy... + */ +#define BLOCKALIGN 32 /* only used in allocb.c */ +#define KSTACK KSTKSIZE + +/* + * Sizes + */ +#define BI2BY 8 /* bits per byte */ +#define BY2SE 4 +#define BY2WD 4 +#define BY2V 8 /* only used in xalloc.c */ + +#define CACHELINESZ 32 +#define PTEMAPMEM (1024*1024) +#define PTEPERTAB (PTEMAPMEM/BY2PG) +#define SEGMAPSIZE 1984 +#define SSEGMAPSIZE 16 +#define PPN(x) ((x)&~(BY2PG-1)) + +/* + * 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 PHYSDRAM 0 + +/* from 0x80000000 up is uncached by L2 (see archkw.c) */ +#define PHYSCESASRAM 0xc8010000 +// #define PHYSSPIFLASH 0xe8000000 /* ignore spi flash */ +/* this address is configured by u-boot, and is 0xd0000000 at reset */ +#define PHYSIO 0xf1000000 /* internal registers */ +#define PHYSCONS (PHYSIO + 0x12000) /* uart */ +#define PHYSNAND1 0xf9000000 /* sheeva/openrd (remapped) */ +#define PHYSNAND2 0xd8000000 /* guru */ +#define PHYSBOOTROM 0xffff0000 /* boot rom */ + +#define FLASHSIZE (512*MiB) /* but not addressed linearly */ + +#define VIRTIO PHYSIO diff --git a/sys/src/9/kw/mkfile b/sys/src/9/kw/mkfile new file mode 100755 index 000000000..8f5402352 --- /dev/null +++ b/sys/src/9/kw/mkfile @@ -0,0 +1,156 @@ +CONF=plug +CONFLIST=plug + +# allegedly u-boot uses the bottom 8MB (up to 0x800000) +# so avoid that +loadaddr=0x60800000 + +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\ + syscallfmt.$O\ + sysfile.$O\ + sysproc.$O\ + taslock.$O\ + tod.$O\ + xalloc.$O\ + +OBJ=\ + l.$O\ + lexception.$O\ + lproc.$O\ + arch.$O\ + cga.$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\ + +# HFILES= + +LIB=\ + /$objtype/lib/libmemlayer.a\ + /$objtype/lib/libmemdraw.a\ + /$objtype/lib/libdraw.a\ + /$objtype/lib/libip.a\ + /$objtype/lib/libsec.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' +# sleep 1 # avoid relinking later + $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' +# sleep 1 # avoid relinking later + $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 + +install-in-flash:V: /$objtype/$p$CONF paqdisk + plug.flash.cfg + echo erase all >/dev/flash/kernelctl + cp /$objtype/$p$CONF /dev/flash/kernel + echo erase all >/dev/flash/plan9ctl + cp paqdisk /dev/flash/plan9 + +/$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 } & + wait + touch $target + +paqdisk: + rm -fr armpaq + mkdir armpaq + cd armpaq + disk/mkfs -d . /sys/lib/sysconfig/proto/armpaqproto + mkpaqfs -o ../paqdisk + cd .. + +<../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 + +archkw.$O devether.$O ether1116.$O ethermii.$O: \ + etherif.h ethermii.h ../port/netif.h +archkw.$O devflash.$O flashkw.$O: ../port/flashif.h +fpi.$O fpiarm.$O fpimem.$O: fpi.h +l.$O lexception.$O lproc.$O mmu.$O: arm.s arm.h mem.h +main.$O: errstr.h init.h reboot.h +mouse.$O: screen.h +devusb.$O: ../port/usb.h +usbehci.$O usbohci.$O usbuhci.$O: ../port/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 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 + {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 + +plug.clean: + rm -rf $p$CONF s$p$CONF armpaq paqdisk $CONF.c boot$CONF.c ../boot/libboot.a5 diff --git a/sys/src/9/kw/mmu.c b/sys/src/9/kw/mmu.c new file mode 100755 index 000000000..2557ff59b --- /dev/null +++ b/sys/src/9/kw/mmu.c @@ -0,0 +1,522 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.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; + + iprint("\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 */ + iprint("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 */ + } + va += MB; + } + if (endva != 0) /* close an open range */ + iprint("l1 maps va (%#lux-%#llux) -> pa %#lux type %#ux\n", + startva, endva-1, startpa, rngtype); +} + +#ifdef CRYPTOSANDBOX +extern uchar sandbox[64*1024+BY2PG]; +#endif + +/* identity map `mbs' megabytes from phys */ +void +mmuidmap(uintptr phys, int mbs) +{ + PTE *l1; + uintptr pa, fpa; + + pa = ttbget(); + l1 = KADDR(pa); + + for (fpa = phys; mbs-- > 0; fpa += MiB) + l1[L1X(fpa)] = fpa|Dom0|L1AP(Krw)|Section; + coherence(); + + mmuinvalidate(); + cacheuwbinv(); + l2cacheuwbinv(); +} + +void +mmuinit(void) +{ + PTE *l1, *l2; + uintptr pa, i; + + pa = ttbget(); + l1 = KADDR(pa); + + /* + * 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 */ + + /* double map vectors at virtual 0 so reset will see them */ + pa -= 1024; + l2 = KADDR(pa); + memset(l2, 0, 1024); + l2[L2X(0)] = PHYSDRAM|L2AP(Krw)|Small; + l1[L1X(0)] = pa|Dom0|Coarse; + + /* + * set up L2 ptes for PHYSIO (i/o registers), with smaller pages to + * enable user-mode access to a few devices. + */ + pa -= 1024; + l2 = KADDR(pa); + /* identity map by default */ + for (i = 0; i < 1024/4; i++) + l2[L2X(VIRTIO + i*BY2PG)] = (PHYSIO + i*BY2PG)|L2AP(Krw)|Small; + +#ifdef CRYPTOSANDBOX + /* + * rest is to let rae experiment with the crypto hardware + */ + /* access to cycle counter */ + l2[L2X(soc.clock)] = soc.clock | L2AP(Urw)|Small; + /* cesa registers; also visible in user space */ + for (i = 0; i < 16; i++) + l2[L2X(soc.cesa + i*BY2PG)] = (soc.cesa + i*BY2PG) | + L2AP(Urw)|Small; + /* crypto sram; remapped to unused space and visible in user space */ + l2[L2X(PHYSIO + 0xa0000)] = PHYSCESASRAM | L2AP(Urw)|Small; + /* 64k of scratch dram */ + for (i = 0; i < 16; i++) + l2[L2X(PHYSIO + 0xb0000 + i*BY2PG)] = + (PADDR((uintptr)sandbox & ~(BY2PG-1)) + i*BY2PG) | + L2AP(Urw) | Small; +#endif + + l1[L1X(VIRTIO)] = pa|Dom0|Coarse; + coherence(); + + mmuinvalidate(); + cacheuwbinv(); + l2cacheuwbinv(); + + m->mmul1 = l1; +// mmudump(l1); /* DEBUG. too early to print */ +} + +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)); + l2cacheuwbse(&l1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE)); + + /* lose any possible stale tlb entries */ + mmuinvalidate(); + +// mmudump(l1); + //print("mmuswitch l1lo %d l1hi %d %d\n", + // m->mmul1lo, m->mmul1hi, proc->kp); +//print("\n"); +} + +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)); + l2cacheuwbse(&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); + l2cacheuwbse((void *)pg->va, BY2PG); + + *l1 = PPN(pg->pa)|Dom0|Coarse; + cachedwbse(l1, sizeof *l1); + l2cacheuwbse(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 %#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]); + l2cacheuwbse(&pte[L2X(va)], sizeof pte[0]); + + /* clear out the current entry */ + mmuinvalidateaddr(PPN(va)); + + /* + * write back dirty entries - we need this because 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 l1 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); + cachedwbse(pte, 4); + l2cacheuwbse(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); + cachedwbse(pte, 4); + l2cacheuwbse(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); + cachedwbse(pte, 4); + l2cacheuwbse(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 + 512*MiB) /* assumes PHYSDRAM is 0 */ + return PHYSDRAM + 512*MiB - 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, BY2PG); + + 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). + */ diff --git a/sys/src/9/kw/notes/movm.w b/sys/src/9/kw/notes/movm.w new file mode 100755 index 000000000..a2b22f1e1 --- /dev/null +++ b/sys/src/9/kw/notes/movm.w @@ -0,0 +1,22 @@ +gorka writes: +--- +I have userspace on the gumstix [xscale, not omap]. The problem that +got me in trouble was that in lexception.s (or l.s), + + MOVM.DB.W [R0-R14], (R13) + +works differently for this architecture (and probably for others, as +it is unclear how it should behave by reading the arm specs). This +happens only for kernel faults as the others (syscall, user faults) +use MOVM.DB.W.S which uses the banked user registers. + +The problem is that in this arch the value of R13 saved is the value +after R13 itself has been modified, whereas in the others (bitsy, +pico...), it was the value before. Adding 4*15 to the stack before +the RFE solves the problem. +--- + +In fact, the 2005 ARM arch. ref. man. (ARM DDI 0100I) says, under STM (1), +that if Rn appears in the set of registers (and isn't the first one) +and .W is specified, the stored value of Rn is unpredictable. +The arm v7-ar arch. ref. man. says such usage is obsolete. diff --git a/sys/src/9/kw/nvram b/sys/src/9/kw/nvram Binary files differnew file mode 100755 index 000000000..a64a5a93f --- /dev/null +++ b/sys/src/9/kw/nvram diff --git a/sys/src/9/kw/openrd.words b/sys/src/9/kw/openrd.words new file mode 100755 index 000000000..99f041231 --- /dev/null +++ b/sys/src/9/kw/openrd.words @@ -0,0 +1,21 @@ +global scale openrd client + +like the sheevaplug, and runs the same kernel, but with these additions: + +2 Gb ethers + +pci bus 0 +00.00.00 vid 11ab did 6281 memory controller sub-class 0x80 +00.01.00 vid 18ca did 0027 display controller sub-class 0 + XGI Technology; Volari Z11 XGI Compatible SVGA controller (xg27 core) + +64MB vga memory, resolution up to 1280x1024 @ 60 Hz. the video +controller is undocumented and even the Linux driver is only available +as a binary, so so far this port is only a cpu kernel. + +run video as normal svga; get framebuffer addr from pci (implement pci). + +uart0 is the shared jtag/uart thing with the usb mini b connector. +uart1 is the rs232/rs485 ports. + +has non-ahci sata without going through pci diff --git a/sys/src/9/kw/plug b/sys/src/9/kw/plug new file mode 100755 index 000000000..7fe107ba6 --- /dev/null +++ b/sys/src/9/kw/plug @@ -0,0 +1,80 @@ +# sheeva plug, openrd-client, guruplug and others +# based on marvell's kirkwood soc +dev + root + cons + env + pipe + proc + mnt + srv + dup + rtc + arch + ssl + tls + cap + kprof + aoe + sd + fs + flash + twsi +# 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 + ether1116 ethermii + archkw + ethermedium +# no flash yet for guruplug + flashkw ecc + loopbackmedium + netdevmedium + usbehci usbehcikw + +ip + tcp + udp + ipifc + icmp + icmp6 + ipmux + +misc + rdb + coproc + sdaoe sdscsi + softfpu + syscall + uartkw + ucalloc +## vgavesa + +port + int cpuserver = 1; + int i8250freq = 3686000; + +boot cpu + tcp +# paq + +bootdir + boot$CONF.out boot + /arm/bin/ip/ipconfig + /arm/bin/auth/factotum +# /arm/bin/paqfs + /arm/bin/usb/usbd +# nvram not needed any longer, it's in flash + nvram diff --git a/sys/src/9/kw/plug.words b/sys/src/9/kw/plug.words new file mode 100755 index 000000000..385b459f4 --- /dev/null +++ b/sys/src/9/kw/plug.words @@ -0,0 +1,104 @@ +global scale sheevaplug & guruplug + +marvell 88f6281 (feroceon kirkwood) SoC +arm926ej-s rev 1 [56251311] (armv5tejl) 1.2GHz cpu + +l1 I & D VIVT caches 16K each: 4-way, 128 sets, 32-byte lines + l1 D is write-through, l1 I is write-back +unified l2 PIPT cache 256K: 4-way, 2048 sets, 32-byte lines + potentially 512K: 8-way + +apparently the mmu walks the page tables in dram and won't look in the +l2 cache. there is no hardware cache coherence, thus the l1 caches +need to be flushed or invalidated when mmu mappings change, but l2 +only needs to be flushed or invalidated around dma operations and page +table changes, and only the affected dma buffers and descriptors or +page table entries need to be flushed or invalidated in l2. + +we arrange that device registers are uncached. + +be aware that cache operations act on cache lines (of CACHELINESZ +bytes) as atomic units, so if you invalidate one word of a cache line, +you invalidate the entire cache line, whether it's been written back +(is clean) or not (is dirty). mixed data structures with parts +maintained by hardware and other parts by software are especially +tricky. we try to pad the initial hardware parts so that the software +parts start in a new cache line. + +there are no video controllers so far, so this port is a cpu +kernel only. + +512MB of dram at physical address 0 +512MB of nand flash +16550 uart for console +see http://www.marvell.com/files/products/embedded_processors/kirkwood/\ + FS_88F6180_9x_6281_OpenSource.pdf, stored locally as + /public/doc/marvell/88f61xx.kirkwood.pdf + +If you plan to use flash, it would be wise to avoid touching the first +megabyte, which contains u-boot, right up to 0x100000. There's a +linux kernel from there to 0x400000, if you care. You'll also likely +want to use paqfs rather than fossil or kfs for file systems in flash +since there is no wear-levelling. + +The code is fairly heavy-handed with the use of barrier instructions +(BARRIERS in assembler, coherence in C), partly in reaction to bad +experience doing Power PC ports, but also just as precautions against +modern processors, which may feel free to execute instructions out of +order or some time later, store to memory out of order or some time +later, otherwise break the model of traditional sequential processors, +or any combination of the above. + +this plan 9 port is based on the port of native inferno to the +sheevaplug by Salva Peiró (saoret.one@gmail.com) and Mechiel Lukkien +(mechiel@ueber.net). + +___ + +# type this once at u-boot, replacing 00504301c49e with your plug's +# mac address; thereafter the plug will pxe boot: +setenv bootdelay 2 +setenv bootcmd 'bootp; bootp; tftp 0x1000 /cfg/pxe/00504301c49e; bootp; tftp 0x800000; go 0x800000' +saveenv + +# see /cfg/pxe/example-kw + + + physical mem map +hex addr size what +---- +0 512MB sdram + +80000000 512MB pcie mem # default +c8010000 2K cesa sram +d0000000 1MB internal regs default address at reset +d8000000 128MB nand flash # actually 512MB addressed through this +e8000000 128MB spi serial flash +f0000000 128MB boot rom # default +f0000000 16MB pcie io # mapped from 0xc0000000 by u-boot + +f1000000 1MB internal regs as mapped by u-boot +f1000000 64K dram regs +f1010000 64K uart, flashes, rtc, gpio, etc. +f1030000 64K crypto accelerator (cesa) +f1040000 64K pci-e regs +f1050000 64K usb otg regs (ehci-like) +f1070000 64K gbe regs +f1080000 64K non-ahci sata regs +f1090000 64K sdio regs + +f8000000 128MB boot device # default, mapped to 0 by u-boot +f8000000 16MB spi flash # mapped by u-boot +f9000000 8MB nand flash # on sheeva/openrd, mapped by u-boot +fb000000 64KB crypto engine +ff000000 16MB boot rom # u-boot + + virtual mem map +hex addr size what +---- +0 512MB user process address space + +60000000 kzero, mapped to 0 +90000000 256MB pcie mem # mapped by u-boot +c0000000 64KB pcie i/o # mapped by u-boot +... as per physical map diff --git a/sys/src/9/kw/random.c b/sys/src/9/kw/random.c new file mode 100755 index 000000000..1f7c0983d --- /dev/null +++ b/sys/src/9/kw/random.c @@ -0,0 +1,138 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" + + +struct Rb +{ + QLock; + Rendez producer; + Rendez consumer; + ulong randomcount; + uchar buf[128]; + uchar *ep; + uchar *rp; + uchar *wp; + uchar next; + uchar wakeme; + ushort bits; + ulong randn; +} rb; + +static int +rbnotfull(void*) +{ + int i; + + i = rb.rp - rb.wp; + return i != 1 && i != (1 - sizeof(rb.buf)); +} + +static int +rbnotempty(void*) +{ + return rb.wp != rb.rp; +} + +static void +genrandom(void*) +{ + up->basepri = PriNormal; + up->priority = up->basepri; + + for(;;){ + for(;;) + if(++rb.randomcount > 100000) + break; + if(anyhigher()) + sched(); + if(!rbnotfull(0)) + sleep(&rb.producer, rbnotfull, 0); + } +} + +/* + * produce random bits in a circular buffer + */ +static void +randomclock(void) +{ + if(rb.randomcount == 0 || !rbnotfull(0)) + return; + + rb.bits = (rb.bits<<2) ^ rb.randomcount; + rb.randomcount = 0; + + rb.next++; + if(rb.next != 8/2) + return; + rb.next = 0; + + *rb.wp ^= rb.bits; + if(rb.wp+1 == rb.ep) + rb.wp = rb.buf; + else + rb.wp = rb.wp+1; + + if(rb.wakeme) + wakeup(&rb.consumer); +} + +void +randominit(void) +{ + addclock0link(randomclock, 1000/HZ); + rb.ep = rb.buf + sizeof(rb.buf); + rb.rp = rb.wp = rb.buf; + kproc("genrandom", genrandom, 0); +} + +/* + * consume random bytes from a circular buffer + */ +ulong +randomread(void *xp, ulong n) +{ + uchar *e, *p; + ulong x; + + p = xp; + + if(waserror()){ + qunlock(&rb); + nexterror(); + } + + qlock(&rb); + for(e = p + n; p < e; ){ + if(rb.wp == rb.rp){ + rb.wakeme = 1; + wakeup(&rb.producer); + sleep(&rb.consumer, rbnotempty, 0); + rb.wakeme = 0; + continue; + } + + /* + * beating clocks will be predictable if + * they are synchronized. Use a cheap pseudo + * random number generator to obscure any cycles. + */ + x = rb.randn*1103515245 ^ *rb.rp; + *p++ = rb.randn = x; + + if(rb.rp+1 == rb.ep) + rb.rp = rb.buf; + else + rb.rp = rb.rp+1; + } + qunlock(&rb); + poperror(); + + wakeup(&rb.producer); + + return n; +} diff --git a/sys/src/9/kw/rebootcode.s b/sys/src/9/kw/rebootcode.s new file mode 100755 index 000000000..0f4a9f98c --- /dev/null +++ b/sys/src/9/kw/rebootcode.s @@ -0,0 +1,172 @@ +/* + * sheevaplug reboot code + * + * 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 */ + + /* copy in arguments from frame */ + MOVW R0, R8 /* entry point */ + MOVW p2+4(FP), R9 /* source */ + MOVW n+8(FP), R10 /* byte count */ + +WAVE('R') + BL cachesoff(SB) + /* now back in 29- or 26-bit addressing, mainly for SB */ + + /* turn the MMU off */ +WAVE('e') + MOVW $KSEGM, R7 + MOVW $PHYSDRAM, R0 + BL _r15warp(SB) + + BIC R7, R12 /* SB */ + BIC R7, R13 /* SP */ + /* don't care about R14 */ + +WAVE('b') + BL mmuinvalidate(SB) +WAVE('o') + BL mmudisable(SB) + +WAVE('o') + MOVW R9, R4 /* restore regs across function calls */ + MOVW R10, R5 + MOVW R8, R6 + + /* 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 R6, 44(SP) /* save dest/entry */ + MOVW R5, 40(SP) /* save count */ + +WAVE('t') + + MOVW R6, 0(SP) + MOVW R6, 4(SP) /* push dest */ + MOVW R6, R0 + MOVW R4, 8(SP) /* push src */ + MOVW R5, 12(SP) /* push size */ + BL memmove(SB) + + MOVW 44(SP), R6 /* restore R6 (dest/entry) */ + MOVW 40(SP), R5 /* restore R5 (count) */ +WAVE('-') + /* + * flush caches + */ + BL cacheuwbinv(SB) + +WAVE('>') +WAVE('\r'); +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 + */ + ORR R6, R6 /* NOP: avoid link bug */ + B (R6) + +/* + * turn the caches off, double map 0 & KZERO, invalidate TLBs, revert to + * tiny addresses. upon return, it will be safe to turn off the mmu. + */ +TEXT cachesoff(SB), 1, $-4 + MOVW $(PsrDirq|PsrDfiq|PsrMsvc), R0 + MOVW R0, CPSR + MOVW $KADDR(0x100-4), R7 /* just before this code */ + MOVW R14, (R7) /* save link */ + + BL cacheuwbinv(SB) + + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + BIC $(CpCwb|CpCicache|CpCdcache|CpCalign), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS + + /* redo double map of 0, KZERO */ + MOVW $(L1+L1X(PHYSDRAM)), R4 /* address of PTE for 0 */ + MOVW $PTEDRAM, R2 /* PTE bits */ +// MOVW $PTEIO, R2 /* PTE bits */ + MOVW $PHYSDRAM, R3 + MOVW $512, 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 + + BARRIERS + MOVW $0, R0 + MCR CpSC, 0, R0, C(CpTLB), C(CpTLBinvd), CpTLBinv + MCR CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv + BARRIERS + + /* back to 29- or 26-bit addressing, mainly for SB */ + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + BIC $(CpCd32|CpCi32), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS + + MOVW $KADDR(0x100-4), R7 /* just before this code */ + MOVW (R7), R14 /* restore link */ + RET + +TEXT _r15warp(SB), 1, $-4 + BIC $KSEGM, R14 + ORR R0, R14 + RET + +TEXT mmudisable(SB), 1, $-4 + MRC CpSC, 0, R0, C(CpCONTROL), C(0) + BIC $(CpChv|CpCmmu|CpCdcache|CpCicache|CpCwb), R0 + MCR CpSC, 0, R0, C(CpCONTROL), C(0) + BARRIERS + RET + +TEXT mmuinvalidate(SB), 1, $-4 /* invalidate all */ + MOVW $0, R0 + MCR CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv + BARRIERS + RET + +TEXT cacheuwbinv(SB), 1, $-4 /* D+I writeback+invalidate */ + BARRIERS + MOVW CPSR, R3 /* splhi */ + ORR $(PsrDirq), R3, R1 + MOVW R1, CPSR + +_uwbinv: /* D writeback+invalidate */ + MRC CpSC, 0, PC, C(CpCACHE), C(CpCACHEwbi), CpCACHEtest + BNE _uwbinv + + MOVW $0, R0 /* I invalidate */ + MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall + /* drain L1 write buffer, also drains L2 eviction buffer on sheeva */ + BARRIERS + + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2flush), CpTCl2all + BARRIERS + MCR CpSC, CpL2, R0, C(CpTESTCFG), C(CpTCl2inv), CpTCl2all + BARRIERS + + MOVW R3, CPSR /* splx */ + RET diff --git a/sys/src/9/kw/softfpu.c b/sys/src/9/kw/softfpu.c new file mode 100755 index 000000000..b412a6bf1 --- /dev/null +++ b/sys/src/9/kw/softfpu.c @@ -0,0 +1,119 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" + +int +fpudevprocio(Proc* proc, void* a, long n, uintptr offset, int write) +{ + /* + * Called from procdevtab.read and procdevtab.write + * allow user process access to the FPU registers. + * This is the only FPU routine which is called directly + * from the port code; it would be nice to have dynamic + * creation of entries in the device file trees... + */ + USED(proc, a, n, offset, write); + + return 0; +} + +void +fpunotify(Ureg*) +{ + /* + * Called when a note is about to be delivered to a + * user process, usually at the end of a system call. + * Note handlers are not allowed to use the FPU so + * the state is marked (after saving if necessary) and + * checked in the Device Not Available handler. + */ +} + +void +fpunoted(void) +{ + /* + * Called from sysnoted() via the machine-dependent + * noted() routine. + * Clear the flag set above in fpunotify(). + */ +} + +void +fpusysrfork(Ureg*) +{ + /* + * Called early in the non-interruptible path of + * sysrfork() via the machine-dependent syscall() routine. + * Save the state so that it can be easily copied + * to the child process later. + */ +} + +void +fpusysrforkchild(Proc*, Ureg*, Proc*) +{ + /* + * Called later in sysrfork() via the machine-dependent + * sysrforkchild() routine. + * Copy the parent FPU state to the child. + */ +} + +void +fpuprocsave(Proc*) +{ + /* + * Called from sched() and sleep() via the machine-dependent + * procsave() routine. + * About to go in to the scheduler. + * If the process wasn't using the FPU + * there's nothing to do. + */ +} + +void +fpuprocrestore(Proc*) +{ + /* + * The process has been rescheduled and is about to run. + * Nothing to do here right now. If the process tries to use + * the FPU again it will cause a Device Not Available + * exception and the state will then be restored. + */ +} + +void +fpusysprocsetup(Proc*) +{ + /* + * Disable the FPU. + * Called from sysexec() via sysprocsetup() to + * set the FPU for the new process. + */ +} + +void +fpuinit(void) +{ +} + +int +fpuemu(Ureg* ureg) +{ + int nfp; + + if(waserror()){ + splhi(); + postnote(up, 1, up->errstr, NDebug); + return 1; + } + spllo(); + nfp = fpiarm(ureg); + splhi(); + poperror(); + + return nfp; +} diff --git a/sys/src/9/kw/syscall.c b/sys/src/9/kw/syscall.c new file mode 100755 index 000000000..d1fcd9fc5 --- /dev/null +++ b/sys/src/9/kw/syscall.c @@ -0,0 +1,354 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" +#include "../port/systab.h" + +#include <tos.h> +#include "ureg.h" + +#include "arm.h" + +typedef struct { + uintptr ip; + Ureg* arg0; + char* arg1; + char msg[ERRMAX]; + Ureg* old; + Ureg ureg; +} NFrame; + +/* + * Return user to state before notify() + */ +static void +noted(Ureg* cur, uintptr arg0) +{ + NFrame *nf; + Ureg *nur; + + qlock(&up->debug); + if(arg0 != NRSTR && !up->notified){ + qunlock(&up->debug); + pprint("call to noted() when not notified\n"); + pexit("Suicide", 0); + } + up->notified = 0; + fpunoted(); + + nf = up->ureg; + + /* sanity clause */ + if(!okaddr(PTR2UINT(nf), sizeof(NFrame), 0)){ + qunlock(&up->debug); + pprint("bad ureg in noted %#p\n", nf); + pexit("Suicide", 0); + } + + /* don't let user change system flags */ + nur = &nf->ureg; + nur->psr &= PsrMask|PsrDfiq|PsrDirq; + nur->psr |= (cur->psr & ~(PsrMask|PsrDfiq|PsrDirq)); + + memmove(cur, nur, sizeof(Ureg)); + + switch((int)arg0){ + case NCONT: + case NRSTR: + if(!okaddr(nur->pc, BY2WD, 0) || !okaddr(nur->sp, BY2WD, 0)){ + qunlock(&up->debug); + pprint("suicide: trap in noted\n"); + pexit("Suicide", 0); + } + up->ureg = nf->old; + qunlock(&up->debug); + break; + case NSAVE: + if(!okaddr(nur->pc, BY2WD, 0) || !okaddr(nur->sp, BY2WD, 0)){ + qunlock(&up->debug); + pprint("suicide: trap in noted\n"); + pexit("Suicide", 0); + } + qunlock(&up->debug); + + splhi(); + nf->arg1 = nf->msg; + nf->arg0 = &nf->ureg; + nf->ip = 0; + cur->sp = PTR2UINT(nf); + break; + default: + pprint("unknown noted arg %#p\n", arg0); + up->lastnote.flag = NDebug; + /*FALLTHROUGH*/ + case NDFLT: + if(up->lastnote.flag == NDebug){ + qunlock(&up->debug); + pprint("suicide: %s\n", up->lastnote.msg); + } + else + qunlock(&up->debug); + pexit(up->lastnote.msg, up->lastnote.flag != NDebug); + } +} + +/* + * Call user, if necessary, with note. + * Pass user the Ureg struct and the note on his stack. + */ +int +notify(Ureg* ureg) +{ + int l; + Note *n; + u32int s; + uintptr sp; + NFrame *nf; + + if(up->procctl) + procctl(up); + if(up->nnote == 0) + return 0; + + fpunotify(ureg); + + s = spllo(); + qlock(&up->debug); + + up->notepending = 0; + n = &up->note[0]; + if(strncmp(n->msg, "sys:", 4) == 0){ + l = strlen(n->msg); + if(l > ERRMAX-23) /* " pc=0x0123456789abcdef\0" */ + l = ERRMAX-23; + snprint(n->msg + l, sizeof n->msg - l, " pc=%#lux", ureg->pc); + } + + if(n->flag != NUser && (up->notified || up->notify == 0)){ + if(n->flag == NDebug) + pprint("suicide: %s\n", n->msg); + qunlock(&up->debug); + pexit(n->msg, n->flag != NDebug); + } + + if(up->notified){ + qunlock(&up->debug); + splhi(); + return 0; + } + + if(up->notify == nil){ + qunlock(&up->debug); + pexit(n->msg, n->flag != NDebug); + } + if(!okaddr(PTR2UINT(up->notify), 1, 0)){ + pprint("suicide: notify function address %#p\n", up->notify); + qunlock(&up->debug); + pexit("Suicide", 0); + } + + sp = ureg->sp - sizeof(NFrame); + if(!okaddr(sp, sizeof(NFrame), 1)){ + qunlock(&up->debug); + pprint("suicide: notify stack address %#p\n", sp); + pexit("Suicide", 0); + } + + nf = UINT2PTR(sp); + memmove(&nf->ureg, ureg, sizeof(Ureg)); + nf->old = up->ureg; + up->ureg = nf; + memmove(nf->msg, up->note[0].msg, ERRMAX); + nf->arg1 = nf->msg; + nf->arg0 = &nf->ureg; + nf->ip = 0; + + ureg->sp = sp; + ureg->pc = PTR2UINT(up->notify); + up->notified = 1; + up->nnote--; + memmove(&up->lastnote, &up->note[0], sizeof(Note)); + memmove(&up->note[0], &up->note[1], up->nnote*sizeof(Note)); + + qunlock(&up->debug); + splx(s); + + return 1; +} + +void +syscall(Ureg* ureg) +{ + char *e; + u32int s; + ulong sp; + long ret; + int i, scallnr; + vlong startns, stopns; + + if(!userureg(ureg)) + panic("syscall: from kernel: pc %#lux r14 %#lux psr %#lux", + ureg->pc, ureg->r14, ureg->psr); + + cycles(&up->kentry); + + m->syscall++; + up->insyscall = 1; + up->pc = ureg->pc; + up->dbgreg = ureg; + + scallnr = ureg->r0; + up->scallnr = scallnr; + if(scallnr == RFORK) + fpusysrfork(ureg); + spllo(); + sp = ureg->sp; + + if(up->procctl == Proc_tracesyscall){ + /* + * Redundant validaddr. Do we care? + * Tracing syscalls is not exactly a fast path... + * Beware, validaddr currently does a pexit rather + * than an error if there's a problem; that might + * change in the future. + */ + if(sp < (USTKTOP-BY2PG) || sp > (USTKTOP-sizeof(Sargs)-BY2WD)) + validaddr(sp, sizeof(Sargs)+BY2WD, 0); + + syscallfmt(scallnr, ureg->pc, (va_list)(sp+BY2WD)); + up->procctl = Proc_stopme; + procctl(up); + if (up->syscalltrace) + free(up->syscalltrace); + up->syscalltrace = nil; + } + + up->nerrlab = 0; + ret = -1; + startns = todget(nil); + if(!waserror()){ + if(scallnr >= nsyscall){ + pprint("bad sys call number %d pc %#lux\n", + scallnr, ureg->pc); + postnote(up, 1, "sys: bad sys call", NDebug); + error(Ebadarg); + } + + if(sp < (USTKTOP-BY2PG) || sp > (USTKTOP-sizeof(Sargs)-BY2WD)) + validaddr(sp, sizeof(Sargs)+BY2WD, 0); + + up->s = *((Sargs*)(sp+BY2WD)); + up->psstate = sysctab[scallnr]; + + /* iprint("%s: syscall %s\n", up->text, sysctab[scallnr]?sysctab[scallnr]:"huh?"); */ + + ret = systab[scallnr](up->s.args); + poperror(); + }else{ + /* failure: save the error buffer for errstr */ + e = up->syserrstr; + up->syserrstr = up->errstr; + up->errstr = e; + } + if(up->nerrlab){ + print("bad errstack [%d]: %d extra\n", scallnr, up->nerrlab); + for(i = 0; i < NERR; i++) + print("sp=%#p pc=%#p\n", + up->errlab[i].sp, up->errlab[i].pc); + panic("error stack"); + } + + /* + * Put return value in frame. On the x86 the syscall is + * just another trap and the return value from syscall is + * ignored. On other machines the return value is put into + * the results register by caller of syscall. + */ + ureg->r0 = ret; + + if(up->procctl == Proc_tracesyscall){ + stopns = todget(nil); + up->procctl = Proc_stopme; + sysretfmt(scallnr, (va_list)(sp+BY2WD), ret, startns, stopns); + s = splhi(); + procctl(up); + splx(s); + if(up->syscalltrace) + free(up->syscalltrace); + up->syscalltrace = nil; + } + + up->insyscall = 0; + up->psstate = 0; + + if(scallnr == NOTED) + noted(ureg, *(ulong*)(sp+BY2WD)); + + splhi(); + if(scallnr != RFORK && (up->procctl || up->nnote)) + notify(ureg); + + /* if we delayed sched because we held a lock, sched now */ + if(up->delaysched){ + sched(); + splhi(); + } + kexit(ureg); +} + +long +execregs(ulong entry, ulong ssize, ulong nargs) +{ + ulong *sp; + Ureg *ureg; + + sp = (ulong*)(USTKTOP - ssize); + *--sp = nargs; + + ureg = up->dbgreg; +// memset(ureg, 0, 15*sizeof(ulong)); + ureg->r13 = (ulong)sp; + ureg->pc = entry; +//print("%lud: EXECREGS pc %#ux sp %#ux nargs %ld\n", up->pid, ureg->pc, ureg->r13, nargs); + + /* + * return the address of kernel/user shared data + * (e.g. clock stuff) + */ + return USTKTOP-sizeof(Tos); +} + +void +sysprocsetup(Proc* p) +{ + fpusysprocsetup(p); +} + +/* + * Craft a return frame which will cause the child to pop out of + * the scheduler in user mode with the return register zero. Set + * pc to point to a l.s return function. + */ +void +forkchild(Proc *p, Ureg *ureg) +{ + Ureg *cureg; + +//print("%lud setting up for forking child %lud\n", up->pid, p->pid); + p->sched.sp = (ulong)p->kstack+KSTACK-sizeof(Ureg); + p->sched.pc = (ulong)forkret; + + cureg = (Ureg*)(p->sched.sp); + memmove(cureg, ureg, sizeof(Ureg)); + + /* syscall returns 0 for child */ + cureg->r0 = 0; + + /* Things from bottom of syscall which were never executed */ + p->psstate = 0; + p->insyscall = 0; + + fpusysrforkchild(p, cureg, up); +} diff --git a/sys/src/9/kw/trap.c b/sys/src/9/kw/trap.c new file mode 100755 index 000000000..f4d2c7756 --- /dev/null +++ b/sys/src/9/kw/trap.c @@ -0,0 +1,671 @@ +/* + * sheevaplug traps, exceptions, interrupts, system calls. + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "ureg.h" +#include "../port/error.h" + +#include "arm.h" + +enum { + Ntimevec = 20, /* # of time buckets for each intr */ + Nvecs = 256, +}; + +extern int notify(Ureg*); + +extern int ldrexvalid; + +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[32]; + +uvlong ninterrupt; +uvlong ninterruptticks; +ulong intrtimes[Nvecs][Ntimevec]; + +typedef struct Handler Handler; +struct Handler { + void (*r)(Ureg*, void*); + void *a; + char name[KNAMELEN]; +}; + +static Handler irqlo[32]; +static Handler irqhi[32]; +static Handler irqbridge[32]; +static Lock irqlock; +static int probing, trapped; + +typedef struct Irq Irq; +struct Irq { + ulong *irq; + ulong *irqmask; + Handler *irqvec; + int nirqvec; + char *name; +}; +/* irq and irqmask are filled in by trapinit */ +static Irq irqs[] = { +[Irqlo] {nil, nil, irqlo, nelem(irqlo), "lo"}, +[Irqhi] {nil, nil, irqhi, nelem(irqhi), "hi"}, +[Irqbridge] {nil, nil, irqbridge, nelem(irqbridge), "bridge"}, +}; + +/* + * keep histogram of interrupt service times + */ +void +intrtime(Mach*, int vno) +{ + ulong diff, x; + + if (m == nil) + return; + x = perfticks(); + diff = x - m->perf.intrts; + m->perf.intrts = x; + + m->perf.inintr += diff; + if(up == nil && m->perf.inidle > diff) + m->perf.inidle -= diff; + + if (m->cpuhz == 0) /* not set yet? */ + return; + diff /= (m->cpuhz/1000000)*100; /* quantum = 100µsec */ + if(diff >= Ntimevec) + diff = Ntimevec-1; + assert(vno >= 0 && vno < Nvecs); + intrtimes[vno][diff]++; +} + +void +intrfmtcounts(char *s, char *se) +{ + USED(s, se); +} + +static void +dumpcounts(void) +{ +} + +void +intrclear(int sort, int v) +{ + *irqs[sort].irq = ~(1 << v); +} + +void +intrmask(int sort, int v) +{ + *irqs[sort].irqmask &= ~(1 << v); +} + +void +intrunmask(int sort, int v) +{ + *irqs[sort].irqmask |= 1 << v; +} + +static void +maskallints(void) +{ + CpucsReg *cpu = (CpucsReg *)soc.cpu; + IntrReg *intr; + + /* no fiq or ep in use */ + intr = (IntrReg *)soc.intr; + intr->lo.irqmask = 0; + intr->hi.irqmask = 0; + cpu->irqmask = 0; + coherence(); +} + +void +intrset(Handler *h, void (*f)(Ureg*, void*), void *a, char *name) +{ + if(h->r != nil) { +// iprint("duplicate irq: %s (%#p)\n", h->name, h->r); + return; + } + h->r = f; + h->a = a; + strncpy(h->name, name, KNAMELEN-1); + h->name[KNAMELEN-1] = 0; +} + +void +intrunset(Handler *h) +{ + h->r = nil; + h->a = nil; + h->name[0] = 0; +} + +void +intrdel(Handler *h, void (*f)(Ureg*, void*), void *a, char *name) +{ + if(h->r != f || h->a != a || strcmp(h->name, name) != 0) + return; + intrunset(h); +} + +void +intrenable(int sort, int v, void (*f)(Ureg*, void*), void *a, char *name) +{ +//iprint("enabling intr %d vec %d for %s\n", sort, v, name); + ilock(&irqlock); + intrset(&irqs[sort].irqvec[v], f, a, name); + intrunmask(sort, v); + iunlock(&irqlock); +} + +void +intrdisable(int sort, int v, void (*f)(Ureg*, void*), void* a, char *name) +{ + ilock(&irqlock); + intrdel(&irqs[sort].irqvec[v], f, a, name); + intrmask(sort, v); + iunlock(&irqlock); +} + +/* + * called by trap to handle interrupts + */ +static void +intrs(Ureg *ur, int sort) +{ + int i, s; + ulong ibits; + Handler *h; + Irq irq; + + assert(sort >= 0 && sort < nelem(irqs)); + irq = irqs[sort]; + ibits = *irq.irq; + ibits &= *irq.irqmask; + + for(i = 0; i < irq.nirqvec && ibits; i++) + if(ibits & (1<<i)){ + h = &irq.irqvec[i]; + if(h->r != nil){ + h->r(ur, h->a); + splhi(); + intrtime(m, sort*32 + i); + if (sort == Irqbridge && i == IRQcputimer0) + m->inclockintr = 1; + ibits &= ~(1<<i); + } + } + if(ibits != 0) { + iprint("spurious irq%s interrupt: %8.8lux\n", irq.name, ibits); + s = splfhi(); + *irq.irq &= ibits; + splx(s); + } +} + +void +intrhi(Ureg *ureg, void*) +{ + intrs(ureg, Irqhi); +} + +void +intrbridge(Ureg *ureg, void*) +{ + intrs(ureg, Irqbridge); + intrclear(Irqlo, IRQ0bridge); +} + +void +trapinit(void) +{ + int i; + CpucsReg *cpu; + IntrReg *intr; + Vectorpage *page0 = (Vectorpage*)HVECTORS; + + intr = (IntrReg *)soc.intr; + cpu = (CpucsReg *)soc.cpu; + irqs[Irqlo].irq = &intr->lo.irq; + irqs[Irqlo].irqmask = &intr->lo.irqmask; + irqs[Irqhi].irq = &intr->hi.irq; + irqs[Irqhi].irqmask = &intr->hi.irqmask; + irqs[Irqbridge].irq = &cpu->irq; + irqs[Irqbridge].irqmask = &cpu->irqmask; + coherence(); + + setr13(PsrMfiq, m->fiqstack + nelem(m->fiqstack)); + setr13(PsrMirq, m->irqstack + nelem(m->irqstack)); + setr13(PsrMabt, m->abtstack + nelem(m->abtstack)); + setr13(PsrMund, m->undstack + nelem(m->undstack)); + + memmove(page0->vectors, vectors, sizeof page0->vectors); + memmove(page0->vtable, vtable, sizeof page0->vtable); + cacheuwbinv(); + l2cacheuwbinv(); + + cpu->cpucfg &= ~Cfgvecinithi; + + for(i = 0; i < nelem(irqlo); i++) + intrunset(&irqlo[i]); + for(i = 0; i < nelem(irqhi); i++) + intrunset(&irqhi[i]); + for(i = 0; i < nelem(irqbridge); i++) + intrunset(&irqbridge[i]); + + /* disable all interrupts */ + intr->lo.fiqmask = intr->hi.fiqmask = 0; + intr->lo.irqmask = intr->hi.irqmask = 0; + intr->lo.epmask = intr->hi.epmask = 0; + cpu->irqmask = 0; + coherence(); + + /* clear interrupts */ + intr->lo.irq = intr->hi.irq = ~0; + cpu->irq = ~0; + coherence(); + + intrenable(Irqlo, IRQ0hisum, intrhi, nil, "hi"); + intrenable(Irqlo, IRQ0bridge, intrbridge, nil, "bridge"); + + /* enable watchdog & access-error interrupts */ + cpu->irqmask |= 1 << IRQcputimerwd | 1 << IRQaccesserr; + coherence(); +} + +static char *trapnames[PsrMask+1] = { + [ PsrMusr ] "user mode", + [ PsrMfiq ] "fiq interrupt", + [ PsrMirq ] "irq interrupt", + [ PsrMsvc ] "svc/swi exception", + [ PsrMabt ] "prefetch abort/data abort", + [ PsrMabt+1 ] "data abort", + [ PsrMund ] "undefined instruction", + [ PsrMsys ] "sys trap", +}; + +static char * +trapname(int psr) +{ + char *s; + + s = trapnames[psr & PsrMask]; + if(s == nil) + s = "unknown trap number in psr"; + return s; +} + +/* + * called by trap to handle access faults + */ +static void +faultarm(Ureg *ureg, uintptr va, int user, int read) +{ + int n, insyscall; + char buf[ERRMAX]; + static int cnt, lastpid; + static ulong lastva; + + if(up == nil) { + dumpregs(ureg); + panic("fault: nil up in faultarm, accessing %#p", va); + } + insyscall = up->insyscall; + up->insyscall = 1; + + /* this is quite helpful during mmu and cache debugging */ + if(va == lastva && up->pid == lastpid) { + ++cnt; + if (cnt >= 2) + /* fault() isn't fixing the underlying cause */ + panic("fault: %d consecutive faults for va %#lux", + cnt+1, va); + } else { + cnt = 0; + lastva = va; + lastpid = up->pid; + } + + 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; +} + +/* + * 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; +} + +void +trap(Ureg *ureg) +{ + int user, x, rv, rem; + ulong inst; + u32int fsr; + uintptr va; + char buf[ERRMAX]; + + if(up != nil) + rem = (char*)ureg - up->kstack; + else + rem = (char*)ureg - ((char*)m + sizeof(Mach)); + if(rem < 256) { + dumpstack(); + panic("trap %d bytes remaining, 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); + } + + if(ureg->type == PsrMabt+1) + ureg->pc -= 8; + else + ureg->pc -= 4; + + m->inclockintr = 0; + switch(ureg->type) { + default: + panic("unknown trap %ld", ureg->type); + break; + case PsrMirq: + ldrexvalid = 0; + // splflo(); /* allow fast interrupts */ + intrs(ureg, Irqlo); + m->intr++; + 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 %#ux pc %#lux addr %#px", + 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", + ureg->pc); + postnote(up, 1, buf, NDebug); + } + }else{ + 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 && m->inclockintr){ + ldrexvalid = 0; + sched(); + splhi(); + } + + if(user){ + if(up->procctl || up->nnote) + notify(ureg); + kexit(ureg); + } +} + +int +isvalidaddr(void *v) +{ + return (uintptr)v >= KZERO; +} + +void +dumplongs(char *msg, ulong *v, int n) +{ + int i, l; + + l = 0; + iprint("%s at %.8p: ", msg, v); + for(i=0; i<n; i++){ + if(l >= 4){ + iprint("\n %.8p: ", v); + l = 0; + } + if(isvalidaddr(v)){ + iprint(" %.8lux", *v++); + l++; + }else{ + iprint(" invalid"); + break; + } + } + iprint("\n"); +} + +static void +dumpstackwithureg(Ureg *ureg) +{ + uintptr l, i, v, estack; + u32int *p; + + iprint("ktrace /kernel/path %#.8lux %#.8lux %#.8lux # pc, sp, link\n", + ureg->pc, ureg->sp, ureg->r14); + delay(2000); + i = 0; + if(up != nil && (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{ + if(up != nil) + iprint("&up->kstack %#p &l %#p\n", up->kstack, &l); + else + iprint("&m %#p &l %#p\n", m, &l); + return; + } + for(l = (uintptr)&l; l < estack; l += sizeof(uintptr)){ + v = *(uintptr*)l; + if(KTZERO < v && v < (uintptr)etext && !(v & 3)){ + v -= sizeof(u32int); /* back up an instr */ + p = (u32int*)v; + if((*p & 0x0f000000) == 0x0b000000){ /* BL instr? */ + iprint("%#8.8lux=%#8.8lux ", l, v); + i++; + } + } + if(i == 4){ + i = 0; + iprint("\n"); + } + } + if(i) + iprint("\n"); +} + +/* + * 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); +} + +void +dumpstack(void) +{ + callwithureg(dumpstackwithureg); +} + +void +dumpregs(Ureg* ureg) +{ + int s; + + if (ureg == nil) { + iprint("trap: no user process\n"); + return; + } + s = splhi(); + iprint("trap: %s", trapname(ureg->type)); + if(ureg != nil && (ureg->psr & PsrMask) != PsrMsvc) + iprint(" in %s", trapname(ureg->psr)); + iprint("\n"); + iprint("psr %8.8lux type %2.2lux pc %8.8lux link %8.8lux\n", + ureg->psr, ureg->type, ureg->pc, ureg->link); + iprint("R14 %8.8lux R13 %8.8lux R12 %8.8lux R11 %8.8lux R10 %8.8lux\n", + ureg->r14, ureg->r13, ureg->r12, ureg->r11, ureg->r10); + iprint("R9 %8.8lux R8 %8.8lux R7 %8.8lux R6 %8.8lux R5 %8.8lux\n", + ureg->r9, ureg->r8, ureg->r7, ureg->r6, ureg->r5); + iprint("R4 %8.8lux R3 %8.8lux R2 %8.8lux R1 %8.8lux R0 %8.8lux\n", + ureg->r4, ureg->r3, ureg->r2, ureg->r1, ureg->r0); + iprint("stack is at %#p\n", ureg); + iprint("pc %#lux link %#lux\n", ureg->pc, ureg->link); + + if(up) + iprint("user stack: %#p-%#p\n", up->kstack, up->kstack+KSTACK-4); + else + iprint("kernel stack: %8.8lux-%8.8lux\n", + (ulong)(m+1), (ulong)m+BY2PG-4); + dumplongs("stack", (ulong *)(ureg + 1), 16); + delay(2000); + dumpstack(); + splx(s); +} + +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; +} diff --git a/sys/src/9/kw/uartkw.c b/sys/src/9/kw/uartkw.c new file mode 100755 index 000000000..0b7e048d0 --- /dev/null +++ b/sys/src/9/kw/uartkw.c @@ -0,0 +1,362 @@ +/* + * marvell kirkwood uart (supposed to be a 16550) + */ +#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/uart.h" + +enum { + UartFREQ = 0, // xxx + + IERrx = 1<<0, + IERtx = 1<<1, + + IRRintrmask = (1<<4)-1, + IRRnointr = 1, + IRRthrempty = 2, + IRRrxdata = 4, + IRRrxstatus = 6, + IRRtimeout = 12, + + IRRfifomask = 3<<6, + IRRfifoenable = 3<<6, + + FCRenable = 1<<0, + FCRrxreset = 1<<1, + FCRtxreset = 1<<2, + /* reserved */ + FCRrxtriggermask = 3<<6, + FCRrxtrigger1 = 0<<6, + FCRrxtrigger4 = 1<<6, + FCRrxtrigger8 = 2<<6, + FCRrxtrigger14 = 3<<6, + + LCRbpcmask = 3<<0, + LCRbpc5 = 0<<0, + LCRbpc6 = 1<<0, + LCRbpc7 = 2<<0, + LCRbpc8 = 3<<0, + LCRstop2b = 1<<2, + LCRparity = 1<<3, + LCRparityeven = 1<<4, + LCRbreak = 1<<6, + LCRdivlatch = 1<<7, + + LSRrx = 1<<0, + LSRrunerr = 1<<1, + LSRparerr = 1<<2, + LSRframeerr = 1<<3, + LSRbi = 1<<4, + LSRthre = 1<<5, + LSRtxempty = 1<<6, + LSRfifoerr = 1<<7, +}; + +extern PhysUart kwphysuart; + +typedef struct UartReg UartReg; +struct UartReg +{ + union { + ulong thr; + ulong dll; + ulong rbr; + }; + union { + ulong ier; + ulong dlh; + }; + union { + ulong iir; + ulong fcr; + }; + ulong lcr; + ulong mcr; + ulong lsr; + ulong scr; +}; + +typedef struct Ctlr Ctlr; +struct Ctlr { + UartReg*regs; + int irq; + Lock; +}; + +static Ctlr kirkwoodctlr[] = { +{ + .regs = nil, /* filled in below */ + .irq = IRQ1uart0, }, +}; + +static Uart kirkwooduart[] = { +{ + .regs = &kirkwoodctlr[0], + .name = "eia0", + .freq = UartFREQ, + .phys = &kwphysuart, + .special= 0, + .console= 1, + .next = nil, }, +}; + +static void +kw_read(Uart *uart) +{ + Ctlr *ctlr = uart->regs; + UartReg *regs = ctlr->regs; + ulong lsr; + char c; + + while ((lsr = regs->lsr) & LSRrx) { + if(lsr&LSRrunerr) + uart->oerr++; + if(lsr&LSRparerr) + uart->perr++; + if(lsr&LSRframeerr) + uart->ferr++; + c = regs->rbr; + if((lsr & (LSRbi|LSRframeerr|LSRparerr)) == 0) + uartrecv(uart, c); + } +} + +static void +kw_intr(Ureg*, void *arg) +{ + Uart *uart = arg; + Ctlr *ctlr = uart->regs; + UartReg *regs = ctlr->regs; + ulong v; + + if(regs == 0) { + kirkwoodctlr[0].regs = (UartReg *)soc.uart[0]; + regs = (UartReg *)soc.uart[0]; /* caution */ + coherence(); + } + v = regs->iir; + if(v & IRRthrempty) + uartkick(uart); + if(v & IRRrxdata) + kw_read(uart); + + intrclear(Irqhi, ctlr->irq); +} + +static Uart* +kw_pnp(void) +{ + kirkwoodctlr[0].regs = (UartReg *)soc.uart[0]; + coherence(); + return kirkwooduart; +} + +static void +kw_enable(Uart* uart, int ie) +{ + Ctlr *ctlr = uart->regs; + UartReg *regs = ctlr->regs; + + if(regs == 0) { + kirkwoodctlr[0].regs = (UartReg *)soc.uart[0]; + regs = (UartReg *)soc.uart[0]; /* caution */ + coherence(); + } + USED(ie); + regs->fcr = FCRenable|FCRrxtrigger4; + regs->ier = IERrx|IERtx; + intrenable(Irqhi, ctlr->irq, kw_intr, uart, uart->name); + + (*uart->phys->dtr)(uart, 1); + (*uart->phys->rts)(uart, 1); +} + +static void +kw_disable(Uart* uart) +{ + Ctlr *ctlr = uart->regs; + + (*uart->phys->dtr)(uart, 0); + (*uart->phys->rts)(uart, 0); + (*uart->phys->fifo)(uart, 0); + + intrdisable(Irqhi, ctlr->irq, kw_intr, uart, uart->name); +} + +static void +kw_kick(Uart* uart) +{ + Ctlr *ctlr = uart->regs; + UartReg *regs = ctlr->regs; + int i; + + if(uart->cts == 0 || uart->blocked) + return; + + for(i = 0; i < 16; i++) { + if((regs->lsr & LSRthre) == 0 || + uart->op >= uart->oe && uartstageoutput(uart) == 0) + break; + regs->thr = *uart->op++; + } +} + +static void +kw_break(Uart* uart, int ms) +{ + USED(uart, ms); +} + +static int +kw_baud(Uart* uart, int baud) +{ + USED(uart, baud); + return 0; +} + +static int +kw_bits(Uart* uart, int bits) +{ + USED(uart, bits); + return 0; +} + +static int +kw_stop(Uart* uart, int stop) +{ + USED(uart, stop); + return 0; +} + +static int +kw_parity(Uart* uart, int parity) +{ + USED(uart, parity); + return 0; +} + +static void +kw_modemctl(Uart* uart, int on) +{ + USED(uart, on); +} + +static void +kw_rts(Uart* uart, int on) +{ + USED(uart, on); +} + +static void +kw_dtr(Uart* uart, int on) +{ + USED(uart, on); +} + +static long +kw_status(Uart* uart, void* buf, long n, long offset) +{ + USED(uart, buf, n, offset); + return 0; +} + +static void +kw_fifo(Uart* uart, int level) +{ + USED(uart, level); +} + +static int +kw_getc(Uart *uart) +{ + Ctlr *ctlr = uart->regs; + UartReg *regs = ctlr->regs; + + while((regs->lsr&LSRrx) == 0) + ; + return regs->rbr; +} + +static void +kw_putc(Uart *uart, int c) +{ + Ctlr *ctlr = uart->regs; + UartReg *regs = ctlr->regs; + + /* can be called from iprint, among many other places */ + if(regs == 0) { + kirkwoodctlr[0].regs = (UartReg *)soc.uart[0]; + regs = (UartReg *)soc.uart[0]; /* caution */ + coherence(); + } + while((regs->lsr&LSRthre) == 0) + ; + regs->thr = c; + coherence(); +} + +PhysUart kwphysuart = { + .name = "kirkwood", + .pnp = kw_pnp, + .enable = kw_enable, + .disable = kw_disable, + .kick = kw_kick, + .dobreak = kw_break, + .baud = kw_baud, + .bits = kw_bits, + .stop = kw_stop, + .parity = kw_parity, + .modemctl = kw_modemctl, + .rts = kw_rts, + .dtr = kw_dtr, + .status = kw_status, + .fifo = kw_fifo, + .getc = kw_getc, + .putc = kw_putc, +}; + +void +uartkirkwoodconsole(void) +{ + Uart *uart; + + uart = &kirkwooduart[0]; + (*uart->phys->enable)(uart, 0); + uartctl(uart, "b115200 l8 pn s1 i1"); + uart->console = 1; + consuart = uart; +//serialputs("uart0 kirkwood\n", strlen("uart0 kirkwood\n")); +} + +void +serialputc(int c) +{ + int cnt, s; + UartReg *regs = (UartReg *)soc.uart[0]; + + s = splhi(); + cnt = m->cpuhz; + if (cnt <= 0) /* cpuhz not set yet? */ + cnt = 1000000; + while((regs->lsr & LSRthre) == 0 && --cnt > 0) + ; + regs->thr = c; + coherence(); + delay(1); + splx(s); +} + +void +serialputs(char *p, int len) +{ + while(--len >= 0) { + if(*p == '\n') + serialputc('\r'); + serialputc(*p++); + } +} diff --git a/sys/src/9/kw/ucalloc.c b/sys/src/9/kw/ucalloc.c new file mode 100755 index 000000000..37e8564c0 --- /dev/null +++ b/sys/src/9/kw/ucalloc.c @@ -0,0 +1,135 @@ +/* + * allocate uncached memory + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" + +#include <pool.h> + +typedef struct Private Private; +struct Private { + Lock; + char msg[256]; + char* cur; +}; + +static Private ucprivate; + +static void +ucpoolpanic(Pool* p, char* fmt, ...) +{ + va_list v; + Private *pv; + char msg[sizeof pv->msg]; + + pv = p->private; + va_start(v, fmt); + vseprint(pv->cur, &pv->msg[sizeof pv->msg], fmt, v); + va_end(v); + memmove(msg, pv->msg, sizeof msg); + iunlock(pv); + panic("%s", msg); +} + +static void +ucpoolprint(Pool* p, char* fmt, ...) +{ + va_list v; + Private *pv; + + pv = p->private; + va_start(v, fmt); + pv->cur = vseprint(pv->cur, &pv->msg[sizeof pv->msg], fmt, v); + va_end(v); +} + +static void +ucpoolunlock(Pool* p) +{ + Private *pv; + char msg[sizeof pv->msg]; + + pv = p->private; + if(pv->cur == pv->msg){ + iunlock(pv); + return; + } + + memmove(msg, pv->msg, sizeof msg); + pv->cur = pv->msg; + iunlock(pv); + + iprint("%.*s", sizeof pv->msg, msg); +} + +static void +ucpoollock(Pool* p) +{ + Private *pv; + + pv = p->private; + ilock(pv); + pv->pc = getcallerpc(&p); + pv->cur = pv->msg; +} + +static void* +ucarena(usize size) +{ + void *uv, *v; + + assert(size == 1*MiB); + + mainmem->maxsize += 1*MiB; + if((v = mallocalign(1*MiB, 1*MiB, 0, 0)) == nil || + (uv = mmuuncache(v, 1*MiB)) == nil){ + free(v); + mainmem->maxsize -= 1*MiB; + return nil; + } + return uv; +} + +static Pool ucpool = { + .name = "Uncached", + .maxsize = 4*MiB, + .minarena = 1*MiB-32, + .quantum = 32, + .alloc = ucarena, + .merge = nil, + .flags = /*POOL_TOLERANCE|POOL_ANTAGONISM|POOL_PARANOIA|*/0, + + .lock = ucpoollock, + .unlock = ucpoolunlock, + .print = ucpoolprint, + .panic = ucpoolpanic, + + .private = &ucprivate, +}; + +void +ucfree(void* v) +{ + if(v == nil) + return; + poolfree(&ucpool, v); +} + +void* +ucalloc(usize size) +{ + assert(size < ucpool.minarena-128); + + return poolallocalign(&ucpool, size, 32, 0, 0); +} + +void* +ucallocalign(usize size, int align, int span) +{ + assert(size < ucpool.minarena-128); + + return poolallocalign(&ucpool, size, align, 0, span); +} diff --git a/sys/src/9/kw/uncached.h b/sys/src/9/kw/uncached.h new file mode 100755 index 000000000..9fffd31fc --- /dev/null +++ b/sys/src/9/kw/uncached.h @@ -0,0 +1,36 @@ +/* + * running the l2 cache as write-back and using cached memory for + * usb data structures yields spurious errors such as + * + * qhintr: td 0x60ee3d80 csw 0x8824a error 0x48 transaction error + * + * from usbehci. so, at least for now, we will use uncached memory until + * we sort out the write-back problems. + */ +#define free ucfree +#define malloc myucalloc +#define mallocz ucallocz +#define smalloc myucalloc +#define xspanalloc ucallocalign + +#define allocb ucallocb +#define iallocb uciallocb +#define freeb ucfreeb + +static void * +ucallocz(uint n, int) +{ + char *p = ucalloc(n); + + if (p) + memset(p, 0, n); + else + panic("ucalloc: out of memory"); + return p; +} + +static void * +myucalloc(uint n) +{ + return ucallocz(n, 1); +} diff --git a/sys/src/9/kw/usbehci.h b/sys/src/9/kw/usbehci.h new file mode 100755 index 000000000..cd1015a8c --- /dev/null +++ b/sys/src/9/kw/usbehci.h @@ -0,0 +1,242 @@ +/* override default macros from ../port/usb.h */ +#undef dprint +#undef ddprint +#undef deprint +#undef ddeprint +#define dprint if(ehcidebug)print +#define ddprint if(ehcidebug>1)print +#define deprint if(ehcidebug || ep->debug)print +#define ddeprint if(ehcidebug>1 || ep->debug>1)print + +typedef struct Ctlr Ctlr; +typedef struct Ecapio Ecapio; +typedef struct Eopio Eopio; +typedef struct Edbgio Edbgio; +typedef struct Isoio Isoio; +typedef struct Poll Poll; +typedef struct Qh Qh; +typedef struct Qtree Qtree; + +#pragma incomplete Ctlr; +#pragma incomplete Ecapio; +#pragma incomplete Eopio; +#pragma incomplete Edbgio; +#pragma incomplete Isoio; +#pragma incomplete Poll; +#pragma incomplete Qh; +#pragma incomplete Qtree; + +/* + * EHCI interface registers and bits + */ +enum +{ + Cnports = 0xF, /* nport bits in Ecapio parms. */ + Cdbgportshift = 20, /* debug port in Ecapio parms. */ + Cdbgportmask = 0xF, + C64 = 1, /* 64-bits, in Ecapio capparms. */ + Ceecpshift = 8, /* extended capabilities ptr. in */ + Ceecpmask = 8, /* the Ecapio capparms reg. */ + Clegacy = 1, /* legacy support cap. id */ + CLbiossem = 2, /* legacy cap. bios sem. */ + CLossem = 3, /* legacy cap. os sem */ + + /* typed links */ + Lterm = 1, + Litd = 0<<1, + Lqh = 1<<1, + Lsitd = 2<<1, + Lfstn = 3<<1, /* we don't use these */ + + /* Cmd reg. */ + Cstop = 0x00000, /* stop running */ + Crun = 0x00001, /* start operation */ + Chcreset = 0x00002, /* host controller reset */ + Cflsmask = 0x0000C, /* frame list size bits */ + Cfls1024 = 0x00000, /* frame list size 1024 */ + Cfls512 = 0x00004, /* frame list size 512 frames */ + Cfls256 = 0x00008, /* frame list size 256 frames */ + Cpse = 0x00010, /* periodic sched. enable */ + Case = 0x00020, /* async sched. enable */ + Ciasync = 0x00040, /* interrupt on async advance doorbell */ + Citc1 = 0x10000, /* interrupt threshold ctl. 1 µframe */ + Citc4 = 0x40000, /* same. 2 µframes */ + /* ... */ + Citc8 = 0x80000, /* same. 8 µframes (can go up to 64) */ + + /* Sts reg. */ + Sasyncss = 0x08000, /* aync schedule status */ + Speriodss = 0x04000, /* periodic schedule status */ + Srecl = 0x02000, /* reclamnation (empty async sched.) */ + Shalted = 0x01000, /* h.c. is halted */ + Sasync = 0x00020, /* interrupt on async advance */ + Sherr = 0x00010, /* host system error */ + Sfrroll = 0x00008, /* frame list roll over */ + Sportchg = 0x00004, /* port change detect */ + Serrintr = 0x00002, /* error interrupt */ + Sintr = 0x00001, /* interrupt */ + Sintrs = 0x0003F, /* interrupts status */ + + /* Intr reg. */ + Iusb = 0x01, /* intr. on usb */ + Ierr = 0x02, /* intr. on usb error */ + Iportchg = 0x04, /* intr. on port change */ + Ifrroll = 0x08, /* intr. on frlist roll over */ + Ihcerr = 0x10, /* intr. on host error */ + Iasync = 0x20, /* intr. on async advance enable */ + Iall = 0x3F, /* all interrupts */ + + /* Config reg. */ + Callmine = 1, /* route all ports to us */ + + /* Portsc reg. */ + Pspresent = 0x00000001, /* device present */ + Psstatuschg = 0x00000002, /* Pspresent changed */ + Psenable = 0x00000004, /* device enabled */ + Pschange = 0x00000008, /* Psenable changed */ + Psresume = 0x00000040, /* resume detected */ + Pssuspend = 0x00000080, /* port suspended */ + Psreset = 0x00000100, /* port reset */ + Pspower = 0x00001000, /* port power on */ + Psowner = 0x00002000, /* port owned by companion */ + Pslinemask = 0x00000C00, /* line status bits */ + Pslow = 0x00000400, /* low speed device */ + + /* Debug port csw reg. */ + Cowner = 0x40000000, /* port owned by ehci */ + Cenable = 0x10000000, /* debug port enabled */ + Cdone = 0x00010000, /* request is done */ + Cbusy = 0x00000400, /* port in use by a driver */ + Cerrmask= 0x00000380, /* error code bits */ + Chwerr = 0x00000100, /* hardware error */ + Cterr = 0x00000080, /* transaction error */ + Cfailed = 0x00000040, /* transaction did fail */ + Cgo = 0x00000020, /* execute the transaction */ + Cwrite = 0x00000010, /* request is a write */ + Clen = 0x0000000F, /* data len */ + + /* Debug port pid reg. */ + Prpidshift = 16, /* received pid */ + Prpidmask = 0xFF, + Pspidshift = 8, /* sent pid */ + Pspidmask = 0xFF, + Ptokshift = 0, /* token pid */ + Ptokmask = 0xFF, + + Ptoggle = 0x00008800, /* to update toggles */ + Ptogglemask = 0x0000FF00, + + /* Debug port addr reg. */ + Adevshift = 8, /* device address */ + Adevmask = 0x7F, + Aepshift = 0, /* endpoint number */ + Aepmask = 0xF, +}; + +/* + * Capability registers (hw) + */ +struct Ecapio +{ + ulong cap; /* 00 controller capability register */ + ulong parms; /* 04 structural parameters register */ + ulong capparms; /* 08 capability parameters */ + ulong portroute; /* 0c not on the CS5536 */ +}; + +/* + * Debug port registers (hw) + */ +struct Edbgio +{ + ulong csw; /* control and status */ + ulong pid; /* USB pid */ + uchar data[8]; /* data buffer */ + ulong addr; /* device and endpoint addresses */ +}; + +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 */ + 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; +}; + +/* + * Kirkwood-specific stuff + */ + +/* + * Operational registers (hw) + */ +struct Eopio +{ + ulong cmd; /* 00 command */ + ulong sts; /* 04 status */ + ulong intr; /* 08 interrupt enable */ + ulong frno; /* 0c frame index */ + ulong seg; /* 10 bits 63:32 of EHCI datastructs (unused) */ + ulong frbase; /* 14 frame list base addr, 4096-byte boundary */ + ulong link; /* 18 link for async list */ + uchar d2c[0x40-0x1c]; /* 1c dummy */ + ulong config; /* 40 1: all ports default-routed to this HC */ + ulong portsc[1]; /* 44 Port status and control, one per port */ + + /* + * these are present on the Kirkwood USB controller. + * are they standard now? Marvell doesn't document them publically. + */ + uchar _pad0[0x64-0x48]; + ulong otgsc; + ulong usbmode; + ulong epsetupsts; + ulong epprime; + ulong epflush; + ulong epsts; + ulong epcompl; + ulong epctl[6]; + + /* freescale registers */ + uchar _pad1[0x2c0-0x98]; + ulong snoop1; + ulong snoop2; + ulong agecntthresh; + ulong prictl; /* priority control */ + ulong sictl; /* system interface control */ + + uchar _pad2[0x3c0-0x2d4]; + ulong ctl; +}; + +extern int ehcidebug; + +void ehcilinkage(Hci *hp); +void ehcimeminit(Ctlr *ctlr); +void ehcirun(Ctlr *ctlr, int on); diff --git a/sys/src/9/kw/usbehcikw.c b/sys/src/9/kw/usbehcikw.c new file mode 100755 index 000000000..ca5b9ceb8 --- /dev/null +++ b/sys/src/9/kw/usbehcikw.c @@ -0,0 +1,350 @@ +/* + * Kirkwood-specific code for + * USB Enhanced Host Controller Interface (EHCI) driver + * High speed USB 2.0. + */ + +#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/usb.h" +#include "usbehci.h" +//#include "uncached.h" + +#define WINTARG(ctl) (((ctl) >> 4) & 017) +#define WINATTR(ctl) (((ctl) >> 8) & 0377) +#define WIN64KSIZE(ctl) (((ctl) >> 16) + 1) + +#define SIZETO64KSIZE(size) ((size) / (64*1024) - 1) + +enum { + Debug = 0, +}; + +typedef struct Kwusb Kwusb; +typedef struct Kwusbtt Kwusbtt; +typedef struct Usbwin Usbwin; + +/* kirkwood usb transaction translator registers? (undocumented) */ +struct Kwusbtt { /* at soc.ehci */ + ulong id; + ulong hwgeneral; + ulong hwhost; + ulong hwdevice; + ulong hwtxbuf; + ulong hwrxbuf; + ulong hwtttxbuf; + ulong hwttrxbuf; +}; + +/* kirkwood usb bridge & phy registers */ +struct Kwusb { /* at offset 0x300 from soc.ehci */ + ulong bcs; /* bridge ctl & sts */ + uchar _pad0[0x310-0x304]; + + ulong bic; /* bridge intr. cause */ + ulong bim; /* bridge intr. mask */ + ulong _pad1; + ulong bea; /* bridge error addr. */ + struct Usbwin { + ulong ctl; /* see Winenable in io.h */ + ulong base; + ulong _pad2[2]; + } win[4]; + ulong phycfg; /* phy config. */ + uchar _pad3[0x400-0x364]; + + ulong pwrctl; /* power control */ + uchar _pad4[0x410-0x404]; + ulong phypll; /* phy pll control */ + uchar _pad5[0x420-0x414]; + ulong phytxctl; /* phy transmit control */ + uchar _pad6[0x430-0x424]; + ulong phyrxctl; /* phy receive control */ + uchar _pad7[0x440-0x434]; + ulong phyivref; /* phy ivref control */ +}; + +static Ctlr* ctlrs[Nhcis]; + +static void +addrmapdump(void) +{ + int i; + ulong ctl, targ, attr, size64k; + Kwusb *map; + Usbwin *win; + + if (!Debug) + return; + map = (Kwusb *)(soc.ehci + 0x300); + for (i = 0; i < nelem(map->win); i++) { + win = &map->win[i]; + ctl = win->ctl; + if (ctl & Winenable) { + targ = WINTARG(ctl); + attr = WINATTR(ctl); + size64k = WIN64KSIZE(ctl); + print("usbehci: address map window %d: " + "targ %ld attr %#lux size %,ld addr %#lux\n", + i, targ, attr, size64k * 64*1024, win->base); + } + } +} + +/* assumes ctlr is ilocked */ +static void +ctlrreset(Ctlr *ctlr) +{ + int i; + Eopio *opio; + + opio = ctlr->opio; + opio->cmd |= Chcreset; + coherence(); + /* wait for it to come out of reset */ + for(i = 0; i < 100 && opio->cmd & Chcreset; i++) + delay(1); + if(i >= 100) + print("ehci %#p controller reset timed out\n", ctlr->capio); + /* + * Marvell errata FE-USB-340 workaround: 1 << 4 magic: + * disable streaming. Magic 3 (usb host mode) from the linux driver + * makes it work. Ick. + */ + opio->usbmode |= 1 << 4 | 3; + coherence(); +} + +/* + * configure window `win' as 256MB dram with attribute `attr' and + * base address + */ +static void +setaddrwin(Kwusb *kw, int win, int attr, ulong base) +{ + kw->win[win].ctl = Winenable | Targdram << 4 | attr << 8 | + SIZETO64KSIZE(256*MB) << 16; + kw->win[win].base = base; +} + +static void +ehcireset(Ctlr *ctlr) +{ + int i, amp, txvdd; + ulong v; + Eopio *opio; + Kwusb *kw; + + ilock(ctlr); + dprint("ehci %#p reset\n", ctlr->capio); + opio = ctlr->opio; + + kw = (Kwusb *)(soc.ehci + 0x300); + kw->bic = 0; + kw->bim = (1<<4) - 1; /* enable all defined intrs */ + ctlrreset(ctlr); + + /* + * 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; + } + + /* requesting more interrupts per µframe may miss interrupts */ + opio->cmd |= Citc8; /* 1 intr. per ms */ + 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); + + /* + * set up the USB address map (bridge address decoding) + */ + for (i = 0; i < nelem(kw->win); i++) + kw->win[i].ctl = kw->win[i].base = 0; + coherence(); + + setaddrwin(kw, 0, Attrcs0, 0); + setaddrwin(kw, 1, Attrcs1, 256*MB); + coherence(); + + if (Debug) + if (kw->bcs & (1 << 4)) + print("usbehci: not swapping bytes\n"); + else + print("usbehci: swapping bytes\n"); + addrmapdump(); /* verify sanity */ + + kw->pwrctl |= 1 << 0 | 1 << 1; /* Pu | PuPll */ + coherence(); + + /* + * Marvell guideline GL-USB-160. + */ + kw->phypll |= 1 << 21; /* VCOCAL_START: PLL calibration */ + coherence(); + microdelay(100); + kw->phypll &= ~(1 << 21); + + v = kw->phytxctl & ~(017 << 27 | 7); /* REG_EXT_FS_RCALL & AMP_2_0 */ + switch (m->socrev) { + default: + print("usbehci: bad 6281 soc rev %d\n", m->socrev); + /* fall through */ + case Socreva0: + amp = 4; + txvdd = 1; + break; + case Socreva1: + amp = 3; + txvdd = 3; + break; + } + /* REG_EXT_FS_RCALL_EN | REG_RCAL_START | AMP_2_0 */ + kw->phytxctl = v | 1 << 26 | 1 << 12 | amp; + coherence(); + microdelay(100); + kw->phytxctl &= ~(1 << 12); + + v = kw->phyrxctl & ~(3 << 2 | 017 << 4); /* LPF_COEF_1_0 & SQ_THRESH_3_0 */ + kw->phyrxctl = v | 1 << 2 | 8 << 4; + + v = kw->phyivref & ~(3 << 8); /* TXVDD12 */ + kw->phyivref = v | txvdd << 8; + coherence(); + + ehcirun(ctlr, 0); + ctlrreset(ctlr); + + iunlock(ctlr); +} + +static void +setdebug(Hci*, int d) +{ + ehcidebug = d; +} + +static void +shutdown(Hci *hp) +{ + Ctlr *ctlr; + Eopio *opio; + + ctlr = hp->aux; + ilock(ctlr); + ctlrreset(ctlr); + + delay(100); + ehcirun(ctlr, 0); + + opio = ctlr->opio; + opio->frbase = 0; + coherence(); + iunlock(ctlr); +} + +static void +findehcis(void) /* actually just use fixed addresses on sheeva */ +{ + int i; + Ctlr *ctlr; + static int already = 0; + + if(already) + return; + already = 1; + + ctlr = smalloc(sizeof(Ctlr)); + /* the sheeva's usb 2.0 otg uses a superset of the ehci registers */ + ctlr->capio = (Ecapio *)(soc.ehci + 0x100); + ctlr->opio = (Eopio *) (soc.ehci + 0x140); + dprint("usbehci: port %#p\n", ctlr->capio); + + for(i = 0; i < Nhcis; i++) + if(ctlrs[i] == nil){ + ctlrs[i] = ctlr; + break; + } + if(i == Nhcis) + print("ehci: bug: more than %d controllers\n", Nhcis); +} + +static int +reset(Hci *hp) +{ + static Lock resetlck; + int i; + Ctlr *ctlr; + Ecapio *capio; + + ilock(&resetlck); + findehcis(); + + /* + * Any adapter matches if no hp->port is supplied, + * otherwise the ports must match. + */ + ctlr = nil; + for(i = 0; i < Nhcis && ctlrs[i] != nil; i++){ + ctlr = ctlrs[i]; + if(ctlr->active == 0) + if(hp->port == 0 || hp->port == (uintptr)ctlr->capio){ + ctlr->active = 1; + break; + } + } + iunlock(&resetlck); + if(ctlrs[i] == nil || i == Nhcis) + return -1; + + hp->aux = ctlr; + hp->port = (uintptr)ctlr->capio; + hp->irq = IRQ0usb0; + hp->tbdf = 0; + + capio = ctlr->capio; + 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); + + /* + * Linkage to the generic HCI driver. + */ + ehcilinkage(hp); + hp->shutdown = shutdown; + hp->debug = setdebug; + return 0; +} + +void +usbehcilink(void) +{ + addhcitype("ehci", reset); +} |