--- /dev/null +++ /sys/src/9/loongson/2f/i8259.c @@ -0,0 +1,216 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +/* + * 8259 interrupt controllers + */ +enum +{ + Int0ctl= 0x20, /* control port (ICW1, OCW2, OCW3) */ + Int0aux= 0x21, /* everything else (ICW2, ICW3, ICW4, OCW1) */ + Int1ctl= 0xA0, /* control port */ + Int1aux= 0xA1, /* everything else (ICW2, ICW3, ICW4, OCW1) */ + + Icw1= 0x10, /* select bit in ctl register */ + Ocw2= 0x00, + Ocw3= 0x08, + + EOI= 0x20, /* non-specific end of interrupt */ + + Elcr1= 0x4D0, /* Edge/Level Triggered Register */ + Elcr2= 0x4D1, +}; + +static Lock i8259lock; +static int i8259mask = 0xFFFF; /* disabled interrupts */ +int i8259elcr; /* mask of level-triggered interrupts */ + +void +i8259init(void) +{ + int x; + uchar dummy; + + ilock(&i8259lock); + + *Pciintrpol = 0x1800; + *Pciintredge &= ~0x1800; + + /* + * Set up the first 8259 interrupt processor. + * Make 8259 interrupts start at CPU vector VectorPIC. + * Set the 8259 as master with edge triggered + * input with fully nested interrupts. + */ + outb(Int0ctl, (1<<4)|(0<<3)|(1<<0)); /* ICW1 - master, edge triggered, + ICW4 will be sent */ + outb(Int0aux, VectorPIC); /* ICW2 - interrupt vector offset */ + outb(Int0aux, 0x04); /* ICW3 - have slave on level 2 */ + outb(Int0aux, 0x01); /* ICW4 - 8086 mode, not buffered */ + + /* + * Set up the second 8259 interrupt processor. + * Make 8259 interrupts start at CPU vector VectorPIC+8. + * Set the 8259 as slave with edge triggered + * input with fully nested interrupts. + */ + outb(Int1ctl, (1<<4)|(0<<3)|(1<<0)); /* ICW1 - master, edge triggered, + ICW4 will be sent */ + outb(Int1aux, VectorPIC+8); /* ICW2 - interrupt vector offset */ + outb(Int1aux, 0x02); /* ICW3 - I am a slave on level 2 */ + outb(Int1aux, 0x01); /* ICW4 - 8086 mode, not buffered */ + outb(Int1aux, (i8259mask>>8) & 0xFF); + + /* + * pass #2 8259 interrupts to #1 + */ + i8259mask &= ~0x04; + outb(Int0aux, i8259mask & 0xFF); + + /* + * Set Ocw3 to return the ISR when ctl read. + * After initialisation status read is set to IRR. + * Read IRR first to possibly deassert an outstanding + * interrupt. + */ + dummy = inb(Int0ctl); + outb(Int0ctl, Ocw3|0x03); + USED(dummy); + dummy = inb(Int1ctl); + outb(Int1ctl, Ocw3|0x03); + USED(dummy); + + /* + * Check for Edge/Level register. + * This check may not work for all chipsets. + * First try a non-intrusive test - the bits for + * IRQs 13, 8, 2, 1 and 0 must be edge (0). If + * that's OK try a R/W test. + */ + x = (inb(Elcr2)<<8)|inb(Elcr1); + if(!(x & 0x2107)){ + outb(Elcr1, 0); + if(inb(Elcr1) == 0){ + outb(Elcr1, 0x20); + if(inb(Elcr1) == 0x20) + i8259elcr = x; + outb(Elcr1, x & 0xFF); + print("i8259: ELCR: %4.4uX\n", i8259elcr); + } + } + iunlock(&i8259lock); +} + +int +i8259isr(int vno) +{ + int irq, isr; + + if(vno < VectorPIC || vno > VectorPIC+MaxIrqPIC) + return 0; + irq = vno-VectorPIC; + + /* + * tell the 8259 that we're done with the + * highest level interrupt (interrupts are still + * off at this point) + */ + ilock(&i8259lock); + isr = inb(Int0ctl); + outb(Int0ctl, EOI); + if(irq >= 8){ + isr |= inb(Int1ctl)<<8; + outb(Int1ctl, EOI); + } + iunlock(&i8259lock); + + return isr & (1< MaxIrqPIC){ + print("i8259enable: irq %d out of range\n", irq); + return -1; + } + irqbit = 1<>8) & 0xFF); + +// if(i8259elcr & irqbit) +// v->eoi = i8259isr; +// else +// v->isr = i8259isr; + iunlock(&i8259lock); + + return VectorPIC+irq; +} + +int +i8259intack(void) +{ + int irq; + + outb(Int0ctl, Ocw3|0x07); /* intr ack on first 8259 */ + irq = inb(Int0ctl) & 7; + if(irq == 2) { /* cascade */ + outb(Int1ctl, Ocw3|0x07); /* intr ack on second 8259 */ + irq = (inb(Int1ctl) & 7) + 8; + } + return irq+VectorPIC; +} + +int +i8259vecno(int irq) +{ + return VectorPIC+irq; +} + +int +i8259disable(int irq) +{ + int irqbit; + + /* + * Given an IRQ, disable the corresponding interrupt + * in the 8259. + */ + if(irq < 0 || irq > MaxIrqPIC){ + print("i8259disable: irq %d out of range\n", irq); + return -1; + } + irqbit = 1<>8) & 0xFF); + } + iunlock(&i8259lock); + return 0; +} --- /dev/null +++ /sys/src/9/loongson/2f/io.h @@ -0,0 +1,335 @@ +enum { + Mhz = 1000*1000, +}; + +/* + * duarts, frequency and registers + */ +#define DUARTFREQ 1843200 + +/* + * interrupt levels on CPU boards. + */ +enum +{ + ILmin = 2, + IL8259 = 2, + ILduart0 = 3, + ILunused4 = 4, + ILunused5 = 5, + ILpci = 6, + ILclock = 7, + ILmax = 7, + + ILshift = 8, +}; + +/* + * PCI configuration regesters and interrupt bits + */ +#define Reset (ulong*)(PCICFG+0x104) +#define Rstcpucold (1<<2) /* cpu cold reset */ + +#define Pcimapcfg (ulong*)(PCICFG+0x118) +#define Pciintrsts (ulong*)(PCICFG+0x13c) +#define Pciintren (ulong*)(PCICFG+0x138) +#define Pciintrenset (ulong*)(PCICFG+0x130) +#define Pciintrenclr (ulong*)(PCICFG+0x134) +#define Pciintrpol (ulong*)(PCICFG+0x12c) +#define Pciintredge (ulong*)(PCICFG+0x124) + +#define Pciintrbase 25 + +/* + * i8259 interrupts + */ +enum { + IrqCLOCK = 0, + IrqKBD = 1, + IrqUART1 = 3, + IrqUART0 = 4, + IrqPCMCIA = 5, + IrqFLOPPY = 6, + IrqLPT = 7, + IrqIRQ7 = 7, + IrqAUX = 12, /* PS/2 port */ + IrqIRQ13 = 13, /* coprocessor on 386 */ + IrqATA0 = 14, + IrqATA1 = 15, + MaxIrqPIC = 15, + + VectorPIC = 0, + MaxVectorPIC = VectorPIC+MaxIrqPIC, +}; + + +/* + * mostly PCI from here on + */ + +typedef struct Pcisiz Pcisiz; +typedef struct Pcidev Pcidev; +typedef struct Vctl Vctl; + +struct Vctl { + Vctl* next; /* handlers on this vector */ + + char name[KNAMELEN]; /* of driver */ + int isintr; /* interrupt or fault/trap */ + int irq; + int tbdf; + int (*isr)(int); /* get isr bit for this irq */ + int (*eoi)(int); /* eoi */ + + void (*f)(Ureg*, void*); /* handler to call */ + void* a; /* argument to call it with */ +}; + +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 */ +}; + +#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) +#define BUSUNKNOWN (-1) + +enum { + MaxEISA = 16, + CfgEISA = 0xC80, +}; + +/* + * 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 */ + + PciBAR0 = 0x10, /* base address */ + PciBAR1 = 0x14, + + PciINTL = 0x3C, /* interrupt line */ + PciINTP = 0x3D, /* interrupt pin */ +}; + +/* 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) */ + Pciscsata = 6, /* SATA */ + + /* 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 */ +}; + +struct Pcisiz +{ + Pcidev* dev; + int siz; + int bar; +}; + +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 */ + uchar intp; /* interrupt pin */ + + 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 */ +}; + +enum { + /* vendor ids */ + Vatiamd = 0x1002, + Vintel = 0x8086, + Vjmicron= 0x197b, + Vmarvell= 0x1b4b, + Vmyricom= 0x14c1, + Vrtl = 0x10ec, + Vsm = 0x126f, +}; + +#define PCIWINDOW 0x80000000 +#define PCIWADDR(va) (PADDR(va)+PCIWINDOW) +#define ISAWINDOW 0 +#define ISAWADDR(va) (PADDR(va)+ISAWINDOW) + +#define PCIMEMADDR(pa) ((pa)|PCIMEM) + +/* SMBus transactions */ +enum +{ + SMBquick, /* sends address only */ + + /* write */ + SMBsend, /* sends address and cmd */ + SMBbytewrite, /* sends address and cmd and 1 byte */ + SMBwordwrite, /* sends address and cmd and 2 bytes */ + + /* read */ + SMBrecv, /* sends address, recvs 1 byte */ + SMBbyteread, /* sends address and cmd, recv's byte */ + SMBwordread, /* sends address and cmd, recv's 2 bytes */ +}; + +typedef struct SMBus SMBus; +struct SMBus { + QLock; /* mutex */ + Rendez r; /* rendezvous point for completion interrupts */ + void *arg; /* implementation dependent */ + ulong base; /* port or memory base of smbus */ + int busy; + void (*transact)(SMBus*, int, int, int, uchar*); +}; + +#pragma varargck type "T" int +#pragma varargck type "T" uint --- /dev/null +++ /sys/src/9/loongson/2f/ln2fbzroot @@ -0,0 +1,68 @@ +dev + root + cons + arch + cap + dup + env + fs + kprof + mnt + pipe + pnp pci + proc + rtc +# sd + srv + ssl + tls + uart + + ether netif + ip arp chandial inferno ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum + + draw screen + kbin kbd latin1 + kbmap + mouse mouse + +# usb + +link + loopbackmedium + ethermedium + ether8139 pci +# usbohci pci + +misc +# uarti8250 +# sdata pci sdscsi + +ip + tcp + udp + ipifc + icmp + icmp6 + gre + ipmux + esp + +port + int cpuserver = 0; + int screenwid = 1024; + int screenht = 600; + int screendepth = 16; + ulong cpufreq = 797*Mhz; + +boot boot + local + tcp + +bootdir + boot$CONF.out boot + /sys/lib/dist/bin/spim/bzfs kfs + /sys/lib/dist/loongson/root.bz2 bzroot + /spim/bin/ip/ipconfig + /spim/bin/auth/factotum +# /spim/bin/usb/usbd --- /dev/null +++ /sys/src/9/loongson/2f/ln2fcpu @@ -0,0 +1,66 @@ +dev + root + cons + arch + cap + dup + env + fs + kprof + mnt + pipe + pnp pci + proc + rtc +# sd + srv + ssl + tls + uart + + ether netif + ip arp chandial inferno ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum + + draw screen + kbin kbd latin1 + kbmap + mouse mouse + +# usb + +link + loopbackmedium + ethermedium + ether8139 pci +# usbohci pci + +misc +# uarti8250 +# sdata pci sdscsi + +ip + tcp + udp + ipifc + icmp + icmp6 + gre + ipmux + esp + +port + int cpuserver = 1; + int screenwid = 1024; + int screenht = 600; + int screendepth = 16; + ulong cpufreq = 797*Mhz; + +boot cpu + tcp + +bootdir + boot$CONF.out boot + /spim/bin/ip/ipconfig + /spim/bin/auth/factotum +# /spim/bin/usb/usbd + nvram --- /dev/null +++ /sys/src/9/loongson/2f/main.c @@ -0,0 +1,758 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../ip/ip.h" +#include +#include +#include +#include "init.h" +#include "reboot.h" + +enum { + /* space for syscall args, return PC, top-of-stack struct */ + Stkheadroom = sizeof(Sargs) + sizeof(uintptr) + sizeof(Tos), +}; + +typedef struct mipsexec Mipsexec; + +#define MAXCONF 64 +#define CONFBUFLEN 4096 +#define MAXBOOTENV 256 + +/* only for reboot */ +#define BOOTARGS ((char*)(CONFADDR)) +#define BOOTARGSLEN CONFBUFLEN +#define CONFARGVLEN (MAXCONF*BY2WD) + +/* args passed by boot loader for main() */ +int _argc; +char **_argv; +char **_env; +static char *bootenvname[MAXBOOTENV]; +static char *bootenvval[MAXBOOTENV]; +static int nenv; + +/* plan9.ini */ +static char confbuf[CONFBUFLEN]; +static char *confname[MAXCONF]; +static char *confval[MAXCONF]; +static int nconf; + +/* + * Option arguments from the command line. + * oargv[0] is the boot file. + */ +static int oargc; +static char* oargv[20]; +static char oargb[128]; +static int oargblen; + +static uintptr sp; /* XXX - must go - user stack of init proc */ + +/* + * software tlb simulation + */ +static Softtlb stlb[MAXMACH][STLBSIZE]; + +Mach *machaddr[MAXMACH]; +Conf conf; +ulong memsize; +FPsave initfp; + +int normalprint; +ulong cpufreq; + +/* + * initialize a processor's mach structure. each processor does this + * for itself. + */ +void +machinit(void) +{ + /* Ensure CU1 is off */ + clrfpintr(); + + m->stb = &stlb[m->machno][0]; + + clockinit(); +} + +static void +optionsinit(char* s) +{ + strecpy(oargb, oargb+sizeof(oargb), s); + + oargblen = strlen(oargb); + oargc = tokenize(oargb, oargv, nelem(oargv)-1); + oargv[oargc] = nil; +} + +void +plan9iniinit(void) +{ + char *cp; + int i; + + nconf = 0; + if(_argv != nil){ + memmove(confbuf, _argv[0], sizeof(confbuf)); + for(i=1; i<_argc && nconf= 0) + return confval[i]; + return nil; +} + +static int +findbootenv(char *name) +{ + int i; + + for(i = 0; i < nenv; i++) + if(cistrcmp(bootenvname[i], name) == 0) + return i; + return -1; +} + +static char* +getbootenv(char *name) +{ + int i; + + i = findbootenv(name); + if(i >= 0) + return bootenvval[i]; + return nil; +} + +void +confinit(void) +{ + char *p; + int i; + ulong kpages, ktop; + ulong maxmem, highmemsize; + + /* memory size */ + memsize = MEMSIZE; + if((p = getbootenv("memsize")) != nil) + memsize = strtoul(p, 0, 0) * MB; + highmemsize = 0; + if((p = getbootenv("highmemsize")) != nil) + highmemsize = strtoul(p, 0, 0) * MB; + if((p = getconf("*maxmem")) != nil){ + maxmem = strtoul(p, 0, 0); + memsize = MIN(memsize, maxmem); + maxmem -= memsize; + highmemsize = MIN(highmemsize, maxmem); + if(memsize < 16*MB) /* sanity */ + memsize = 16*MB; + } + + /* set up other configuration parameters */ + conf.nproc = 2000; + conf.nswap = 262144; + conf.nswppo = 4096; + conf.nimage = 200; + + /* + * divide memory to user pages and kernel. + */ + conf.mem[0].base = ktop = PADDR(PGROUND((ulong)end)); + conf.mem[0].npage = memsize/BY2PG - ktop/BY2PG; + + conf.mem[1].base = HIGHMEM; + conf.mem[1].npage = highmemsize/BY2PG; + + conf.npage = 0; + for(i=0; i (64*MB + conf.npage*sizeof(Page))/BY2PG){ + kpages = (64*MB + conf.npage*sizeof(Page))/BY2PG; + kpages += (conf.nproc*KSTACK)/BY2PG; + } + conf.upages = conf.npage - kpages; + conf.ialloc = (kpages/2)*BY2PG; + + kpages *= BY2PG; + kpages -= conf.upages*sizeof(Page) + + conf.nproc*sizeof(Proc) + + conf.nimage*sizeof(Image) + + conf.nswap + + conf.nswppo*sizeof(Page); + mainmem->maxsize = kpages; + + /* + * set up CPU's mach structure + * cpu0's was zeroed in main() and our stack is in Mach, so don't zero it. + */ + m->machno = 0; + m->hz = cpufreq; /* initial guess at MHz */ + m->speed = m->hz / Mhz; + conf.nmach = 1; + + conf.nuart = 1; + conf.copymode = 0; /* copy on write */ +} + +void +procrestore(Proc *p) +{ + uvlong t; + + if(p->kp) + return; + cycles(&t); + p->pcycles -= t; +} + +/* + * Save the mach dependent part of the process state. + */ +void +procsave(Proc *p) +{ + uvlong t; + + cycles(&t); + p->pcycles += t; + //fpuprocsave(); // XXX +} + +static void +fmtinit(void) +{ + printinit(); + quotefmtinstall(); + /* ipreset installs these when chandevreset runs */ + fmtinstall('i', eipfmt); + fmtinstall('I', eipfmt); + fmtinstall('E', eipfmt); + fmtinstall('V', eipfmt); + fmtinstall('M', eipfmt); +} + +/* + * setup MIPS trap vectors + */ +void +vecinit(void) +{ + memmove((ulong*)UTLBMISS, (ulong*)vector0, 0x80); + memmove((ulong*)XEXCEPTION, (ulong*)vector0, 0x80); + memmove((ulong*)CACHETRAP, (ulong*)vector100, 0x80); + memmove((ulong*)EXCEPTION, (ulong*)vector180, 0x80); + /* 0x200 not used in looongson 2e */ + icflush((ulong*)UTLBMISS, 4*1024); + + setstatus(getstatus() & ~BEV); +} + +static void +prcpuid(void) +{ + ulong cpuid, cfg; + char *cpu; + + cpuid = prid(); + cfg = getconfig(); + if (((cpuid>>16) & MASK(8)) == 0) /* vendor */ + cpu = "old mips"; + else if (((cpuid>>16) & MASK(8)) == 1) + switch ((cpuid>>8) & MASK(8)) { /* processor */ + case 0x93: + cpu = "mips 24k"; + break; + case 0x96: + cpu = "mips 24ke"; + break; + default: + cpu = "mips"; + break; + } + else + cpu = "other mips"; + delay(200); + print("cpu%d: %ldMHz %s %se 0x%ulx v%ld.%ld rev %ld has fpu\n", + m->machno, m->hz / Mhz, cpu, cfg & (1<<15)? "b": "l", + (cpuid>>8) & MASK(8), (cpuid>>5) & MASK(3), (cpuid>>2) & MASK(3), + cpuid & MASK(2)); + delay(200); + + print("cpu%d: %d tlb entries, using %dK pages\n", m->machno, + 64, BY2PG/1024); + delay(50); + print("cpu%d: l1 i cache: %d sets 4 ways 32 bytes/line\n", m->machno, + 32 << ((cfg>>9) & MASK(3))); + delay(50); + print("cpu%d: l1 d cache: %d sets 4 ways 32 bytes/line\n", m->machno, + 32 << ((cfg>>6) & MASK(3))); + delay(500); +} + +static int +ckpagemask(ulong mask, ulong size) +{ + int s; + ulong pm; + + s = splhi(); + setpagemask(mask); + pm = getpagemask(); + splx(s); + if(pm != mask){ + iprint("page size %ldK not supported on this cpu; " + "mask %#lux read back as %#lux\n", size/1024, mask, pm); + return -1; + } + return 0; +} + +void +init0(void) +{ + char buf[128]; + int i; + + up->nerrlab = 0; + + 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()){ + ksetenv("cputype", "spim", 0); + snprint(buf, sizeof buf, "spim %s loongson 2f", conffile); + ksetenv("terminal", buf, 0); + if(cpuserver) + ksetenv("service", "cpu", 0); + else + ksetenv("service", "terminal", 0); +// ksetenv("nobootprompt", "local!/boot/bzroot", 0); // for bzroot + ksetenv("nvram", "/boot/nvram", 0); + /* convert plan9.ini variables to #e and #ec */ + for(i = 0; i < nconf; i++) { + if(*confname[i] == '*') + continue; + ksetenv(confname[i], confval[i], 0); + ksetenv(confname[i], confval[i], 1); + } + poperror(); + } + kproc("alarm", alarmkproc, 0); +// i8250console(); + 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 - Stkheadroom - i)); + memmove(p, oargb, i); + + /* + * Now push the argv pointers. + * The code jumped to by touser in l.s expects arguments + * main(char* argv0, ...) + * and calls + * startboot("/boot/boot", &argv0) + * not the usual (int argc, char* argv[]) + */ + av = (char**)(p - (oargc+1)*sizeof(char*)); + ssize = base + BY2PG - PTR2UINT(av); + for(i = 0; i < oargc; i++) + *av++ = (oargv[i] - oargb) + (p - base) + (USTKTOP - BY2PG); + *av = nil; + sp = USTKTOP - ssize; +} + +void +userinit(void) +{ + Proc *p; + KMap *k; + Page *pg; + Segment *s; + + 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); + + p->fpstate = FPinit; + p->fpsave.fpstatus = initfp.fpstatus; + + /* + * Kernel Stack + */ + p->sched.pc = (ulong)init0; + p->sched.sp = (ulong)p->kstack+KSTACK-Stkheadroom; + 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); + memset(pg->cachectl, PG_DATFLUSH, sizeof(pg->cachectl)); + 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]); + memset((void*)VA(k), 0, BY2PG); + memmove((ulong*)VA(k), initcode, sizeof initcode); + kunmap(k); + + ready(p); +} + +/* called from rebootcmd() */ +int +parsemipsboothdr(Chan *c, ulong magic, Execvals *evp) +{ + long extra; + Mipsexec me; + + /* + * BOOT_MAGIC is sometimes defined like this: + * #define BOOT_MAGIC (0x160<<16) || magic == ((0x160<<16)|3) + * so we can only use it in a fairly stylized manner. + */ + if(magic == BOOT_MAGIC) { + c->offset = 0; /* back up */ + readn(c, &me, sizeof me); + /* if binary is -H1, read an extra long */ + if (l2be(me.amagic) == 0407 && me.nscns == 0) + readn(c, &extra, sizeof extra); + evp->entry = l2be(me.mentry); + evp->textsize = l2be(me.tsize); + evp->datasize = l2be(me.dsize); + return 0; + } else + return -1; +} + +void +main(void) +{ + extern char edata[], end[]; + + m = (Mach*)MACHADDR; + memset(m, 0, sizeof(Mach)); /* clear Mach */ + m->machno = 0; + machaddr[m->machno] = m; + up = nil; + memset(edata, 0, end-edata); /* clear bss */ + + optionsinit("/boot/boot boot"); + plan9iniinit(); + confinit(); + savefpregs(&initfp); + + machinit(); + active.exiting = 0; + active.machs = 1; + + kmapinit(); + xinit(); + screeninit(); + + ckpagemask(PGSZ, BY2PG); + tlbinit(); + machwire(); + + timersinit(); + fmtinit(); + vecinit(); + + normalprint = 1; + print("\nPlan 9\n"); + print("\0\0\0\0\0\0\0"); // XXX 0c bug fixed -- leave this for sanity + prcpuid(); + if(PTECACHABILITY == PTENONCOHERWT) + print("caches configured as write-through\n"); +// xsummary(); + + i8259init(); + kbdinit(); + if(conf.monitor) + swcursorinit(); + + pageinit(); + procinit0(); + initseg(); + links(); + chandevreset(); + + swapinit(); + userinit(); + parseboothdr = parsemipsboothdr; + schedinit(); + panic("schedinit returned"); +} + +static void +writeconf(void) +{ + char *p, *q; + int n; + + p = getconfenv(); + + if(waserror()) { + free(p); + nexterror(); + } + + /* convert to name=value\0 format */ + _argc = 1; /* skip _argv[0] */ + _argv = (void*)CONFARGV; + _argv[0] = BOOTARGS; + for(q=p; *q; q++) { + _argv[_argc] = (char*)((ulong)BOOTARGS + (q - p)); + _argc++; + q += strlen(q); + *q = '='; + q += strlen(q); + } + n = q - p + 1; + _env = &_argv[_argc]; + + if(n >= BOOTARGSLEN || (ulong)_env >= CONFARGV + CONFARGVLEN) + error("kernel configuration too large"); + memmove(BOOTARGS, p, n); + memset(BOOTARGS + n, '\0', BOOTARGSLEN - n); + memset(_env, 0, CONFARGV + CONFARGVLEN - (ulong)_env); + + poperror(); + free(p); +} + +static void +shutdown(int ispanic) +{ + int ms, once; + + ilock(&active); + if(ispanic) + active.ispanic = ispanic; + else if(m->machno == 0 && (active.machs & (1<machno)) == 0) + active.ispanic = 0; + once = active.machs & (1<machno); + /* + * setting exiting will make hzclock() on each processor call exit(0), + * which calls shutdown(0) and idles non-bootstrap cpus and returns + * on bootstrap processors (to permit a reboot). clearing our bit + * in machs avoids calling exit(0) from hzclock() on this processor. + */ + active.machs &= ~(1<machno); + active.exiting = 1; + iunlock(&active); + + if(once) { + delay(m->machno*1000); /* stagger them */ + iprint("cpu%d: exiting\n", m->machno); + } + spllo(); + for(ms = MAXMACH * 1000; ms > 0; ms -= TK2MS(2)){ + delay(TK2MS(2)); + if(active.machs == 0 && consactive() == 0) + break; + } + delay(100); +} + +void +exit(int ispanic) +{ + int timer; + + delay(1000); + shutdown(ispanic); + timer = 0; + while(active.machs || consactive()) { + if(timer++ > 400) + break; + delay(10); + } + delay(1000); + splhi(); + + setstatus(BEV); + coherence(); + + iprint("exit: awaiting reset\n"); + delay(1000); /* await a reset */ + + if(!active.ispanic) { + iprint("exit: jumping to rom\n"); +// *Reset |= Rstcpucold; /* cpu cold reset */ + outb(0x381, 0xf4); + outb(0x382, 0xec); + outb(0x383, 1); + } + + iprint("exit: looping\n"); + for(;;); +} + +/* + * 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, ulong); + + writeconf(); + + /* + * the boot processor is cpu0. execute this function on it + * so that the new kernel has the same cpu0. + */ + if(m->machno != 0) { + procwired(up, 0); + sched(); + } + if(m->machno != 0) + print("on cpu%d (not 0)!\n", m->machno); + + if(code == nil || entry == nil) + exit(1); + + shutdown(0); + + /* + * should be the only processor running now + */ + iprint("reboot: entry %#p code %#p size %ld\n", entry, code, size); + iprint("code[0] = %#lux\n", *(ulong *)code); + + /* turn off buffered serial console */ + serialoq = nil; + kprintoq = nil; + screenputs = nil; + + /* shutdown devices */ + chandevshutdown(); + + splhi(); + intrshutdown(); + + /* setup reboot trampoline function */ + f = (void*)REBOOTADDR; + memmove(f, rebootcode, sizeof(rebootcode)); + icflush(f, sizeof(rebootcode)); + + setstatus(BEV); /* also, kernel mode, no interrupts */ + coherence(); + + /* off we go - never to return */ + (*f)((ulong)entry, (ulong)code, size, _argc); + + panic("loaded kernel returned!"); +} + +/* + * stub for ../port/devpnp.c + */ +int +isaconfig(char *class, int ctlrno, ISAConf *isa) +{ + USED(class, ctlrno, isa); + return 0; +} --- /dev/null +++ /sys/src/9/loongson/2f/mem.h @@ -0,0 +1,346 @@ +/* + * Memory and machine-specific definitions. Used in C and assembler. + */ + +/* + * Sizes + */ + +#define BI2BY 8 /* bits per byte */ +#define BI2WD 32 /* bits per word */ +#define BY2WD 4 /* bytes per word */ +#define BY2V 8 /* bytes per vlong */ + +#define MAXBY2PG (16*1024) /* rounding for UTZERO in executables; see mkfile */ +#define UTROUND(t) ROUNDUP((t), MAXBY2PG) + +#define BIGPAGES /* use 16K page */ +#ifndef BIGPAGES +#define BY2PG 4096 /* bytes per page */ +#define PGSHIFT 12 /* log2(BY2PG) */ +#define PGSZ PGSZ4K +#define MACHSIZE (2*BY2PG) +#else +#define BY2PG (16*1024) /* bytes per page */ +#define PGSHIFT 14 /* log2(BY2PG) */ +#define PGSZ PGSZ16K +#define MACHSIZE BY2PG +#endif + +#define KSTACK 8192 /* Size of kernel stack */ +#define WD2PG (BY2PG/BY2WD) /* words per page */ + +#define MAXMACH 1 /* max # cpus system can run; see active.machs */ +#define STACKALIGN(sp) ((sp) & ~7) /* bug: assure with alloc */ +#define BLOCKALIGN 16 +#define CACHELINESZ 32 /* loongson 2e */ +#define ICACHESIZE (64*1024) /* loongson 2e */ +#define DCACHESIZE (64*1024) /* loongson 2e */ +#define SCACHESIZE (512*1024) /* L2 cache, loongson 2e */ + +#define MASK(w) FMASK(0, w) + +/* + * Time + */ +#define HZ 100 /* clock frequency */ +#define MS2HZ (1000/HZ) /* millisec per clock tick */ +#define TK2SEC(t) ((t)/HZ) /* ticks to seconds */ + +/* + * CP0 registers + */ + +#define INDEX 0 +#define RANDOM 1 +#define TLBPHYS0 2 /* aka ENTRYLO0 */ +#define TLBPHYS1 3 /* aka ENTRYLO1 */ +#define CONTEXT 4 +#define PAGEMASK 5 +#define WIRED 6 +#define BADVADDR 8 +#define COUNT 9 +#define TLBVIRT 10 /* aka ENTRYHI */ +#define COMPARE 11 +#define STATUS 12 +#define CAUSE 13 +#define EPC 14 +#define PRID 15 +#define CONFIG 16 +#define LLADDR 17 +#define WATCHLO 18 +#define WATCHHI 19 +#define XCONTEXT 20 +#define DIAGNOSE 22 /* loongson 2e */ +#define PERFCTL 24 +#define PERFCOUNT 25 +#define CACHEECC 26 +#define CACHEERR 27 +#define TAGLO 28 +#define TAGHI 29 +#define ERROREPC 30 + +/* + * M(STATUS) bits + */ +#define KMODEMASK 0x0000001f +#define IE 0x00000001 /* master interrupt enable */ +#define EXL 0x00000002 /* exception level */ +#define ERL 0x00000004 /* error level */ +#define KSUPER 0x00000008 +#define KUSER 0x00000010 +#define KSU 0x00000018 +#define UX 0x00000020 /* no [USK]X 64-bit extension bits on 24k */ +#define SX 0x00000040 +#define KX 0x00000080 +#define INTMASK 0x0000ff00 +#define INTR0 0x00000100 /* interrupt enable bits */ +#define INTR1 0x00000200 +#define INTR2 0x00000400 +#define INTR3 0x00000800 +#define INTR4 0x00001000 +#define INTR5 0x00002000 +#define INTR6 0x00004000 +#define INTR7 0x00008000 +//#define DE 0x00010000 /* not on 24k */ +#define TS 0x00200000 /* tlb shutdown; on 24k at least */ +#define BEV 0x00400000 /* bootstrap exception vectors */ +#define RE 0x02000000 /* reverse-endian in user mode */ +#define FR 0x04000000 /* enable 32 FP regs */ +#define CU0 0x10000000 +#define CU1 0x20000000 /* FPU enable */ + +/* + * M(CONFIG) bits + */ + +#define CFG_K0 7 /* kseg0 cachability */ + +/* + * M(CAUSE) bits + */ + +#define BD (1<<31) /* last excep'n occurred in branch delay slot */ + +/* + * Exception codes + */ +#define EXCMASK 0x1f /* mask of all causes */ +#define CINT 0 /* external interrupt */ +#define CTLBM 1 /* TLB modification: store to unwritable page */ +#define CTLBL 2 /* TLB miss (load or fetch) */ +#define CTLBS 3 /* TLB miss (store) */ +#define CADREL 4 /* address error (load or fetch) */ +#define CADRES 5 /* address error (store) */ +#define CBUSI 6 /* bus error (fetch) */ +#define CBUSD 7 /* bus error (data load or store) */ +#define CSYS 8 /* system call */ +#define CBRK 9 /* breakpoint */ +#define CRES 10 /* reserved instruction */ +#define CCPU 11 /* coprocessor unusable */ +#define COVF 12 /* arithmetic overflow */ +#define CTRAP 13 /* trap */ +#define CVCEI 14 /* virtual coherence exception (instruction) */ +#define CFPE 15 /* floating point exception */ +#define CTLBRI 19 /* tlb read-inhibit */ +#define CTLBXI 20 /* tlb execute-inhibit */ +#define CWATCH 23 /* watch exception */ +#define CMCHK 24 /* machine checkcore */ +#define CCACHERR 30 /* cache error */ +#define CVCED 31 /* virtual coherence exception (data) */ + +/* + * M(CACHEECC) a.k.a. ErrCtl bits + */ +#define PE (1<<31) +#define LBE (1<<25) +#define WABE (1<<24) + +/* + * FCR31 bits, complement to u.h + */ +#define FPCINEX (1<<12) /* causes */ +#define FPCOVFL (1<<13) +#define FPCUNFL (1<<14) +#define FPCZDIV (1<<15) +#define FPCINVAL (1<<16) +#define FPUNIMP (1<<17) +#define FPEXCMASK (0x3f<<12) + +#define FPFLUSH (1<<24) + +/* + * Trap vectors + */ + +#define UTLBMISS (KSEG0+0x000) +#define XEXCEPTION (KSEG0+0x080) +#define CACHETRAP (KSEG0+0x100) +#define EXCEPTION (KSEG0+0x180) + +/* + * Magic registers + */ + +#define USER 24 /* R24 is up-> */ +#define MACH 25 /* R25 is m-> */ + +/* + * offsets in ureg.h for l.s + */ +#define Ureg_status (Uoffset+0) +#define Ureg_pc (Uoffset+4) +#define Ureg_sp (Uoffset+8) +#define Ureg_cause (Uoffset+12) +#define Ureg_badvaddr (Uoffset+16) +#define Ureg_tlbvirt (Uoffset+20) + +#define Ureg_hi (Uoffset+24) +#define Ureg_lo (Uoffset+28) +#define Ureg_r31 (Uoffset+32) +#define Ureg_r30 (Uoffset+36) +#define Ureg_r28 (Uoffset+40) +#define Ureg_r27 (Uoffset+44) +#define Ureg_r26 (Uoffset+48) +#define Ureg_r25 (Uoffset+52) +#define Ureg_r24 (Uoffset+56) +#define Ureg_r23 (Uoffset+60) +#define Ureg_r22 (Uoffset+64) +#define Ureg_r21 (Uoffset+68) +#define Ureg_r20 (Uoffset+72) +#define Ureg_r19 (Uoffset+76) +#define Ureg_r18 (Uoffset+80) +#define Ureg_r17 (Uoffset+84) +#define Ureg_r16 (Uoffset+88) +#define Ureg_r15 (Uoffset+92) +#define Ureg_r14 (Uoffset+96) +#define Ureg_r13 (Uoffset+100) +#define Ureg_r12 (Uoffset+104) +#define Ureg_r11 (Uoffset+108) +#define Ureg_r10 (Uoffset+112) +#define Ureg_r9 (Uoffset+116) +#define Ureg_r8 (Uoffset+120) +#define Ureg_r7 (Uoffset+124) +#define Ureg_r6 (Uoffset+128) +#define Ureg_r5 (Uoffset+132) +#define Ureg_r4 (Uoffset+136) +#define Ureg_r3 (Uoffset+140) +#define Ureg_r2 (Uoffset+144) +#define Ureg_r1 (Uoffset+148) + +/* ch and carrera used these defs */ + /* Sizeof(Ureg) + (R5,R6) + 16 bytes slop + retpc + ur */ +// #define UREGSIZE ((Ureg_r1+4-Uoffset) + 2*BY2V + 16 + BY2WD + BY2WD) +// #define Uoffset 8 + +// #define UREGSIZE (Ureg_r1 + 4 - Uoffset) /* this ought to work */ +#define UREGSIZE ((Ureg_r1+4-Uoffset) + 2*BY2V + 16 + BY2WD + BY2WD) +#define Uoffset 0 +#define Notuoffset 8 + +/* + * MMU + */ +#define PGSZ4K (0x00<<13) +#define PGSZ16K (0x03<<13) +#define PGSZ64K (0x0F<<13) +#define PGSZ256K (0x3F<<13) +#define PGSZ1M (0xFF<<13) +#define PGSZ4M (0x3FF<<13) +#define PGSZ8M (0x7FF<<13) /* not on loongson 2e */ +#define PGSZ16M (0xFFF<<13) +#define PGSZ64M (0x3FFF<<13) /* not on loongson 2e */ +#define PGSZ256M (0xFFFF<<13) /* not on loongson 2e */ + +/* mips address spaces, tlb-mapped unless marked otherwise */ +#define KUSEG 0x00000000 /* user process */ +#define KSEG0 0x80000000 /* kernel (direct mapped, cached) */ +#define KSEG1 0xA0000000 /* kernel (direct mapped, uncached: i/o) */ +#define KSEG2 0xC0000000 /* kernel, used for TSTKTOP */ +#define KSEG3 0xE0000000 /* kernel, used by kmap */ +#define KSEGM 0xE0000000 /* mask to check which seg */ + +/* + * Fundamental addresses + */ + +#define CONFADDR (KZERO+0x1000) /* just above vectors, only for reboot */ +#define CONFARGV (KZERO+0x2000) /* used by assembler */ +#define REBOOTADDR (KZERO+0x3000) +#define MACHADDR (KTZERO-MAXMACH*MACHSIZE) /* Mach structures */ +#define MACHP(n) ((Mach *)(MACHADDR+(n)*MACHSIZE)) +#define ROM 0xbfc00000 +#define KMAPADDR 0xE0000000 /* kmap'd addresses */ +#define WIREDADDR 0xE2000000 /* address wired kernel space */ + +#define PHYSCONS (KSEG1|0x1ff003f8) /* i8250 uart */ + +#define PIDXSHFT 12 +#ifndef BIGPAGES +#define NCOLOR 8 +#define PIDX ((NCOLOR-1)<>PIDXSHFT) % NCOLOR) +#else +/* no cache aliases are possible with pages of 16K or larger */ +#define NCOLOR 1 +#define PIDX 0 +#define getpgcolor(a) 0 +#endif +#define KMAPSHIFT 15 + +#define PTEGLOBL (1<<0) +#define PTEVALID (1<<1) +#define PTEWRITE (1<<2) +#define PTERONLY 0 +#define PTEALGMASK (7<<3) +#define PTENONCOHERWT (0<<3) /* cached, write-through (slower) */ +#define PTEUNCACHED (2<<3) +#define PTENONCOHERWB (3<<3) /* cached, write-back */ +#define PTEUNCACHEDACC (7<<3) +/* rest are reserved on 24k */ +#define PTECOHERXCL (4<<3) +#define PTECOHERXCLW (5<<3) +#define PTECOHERUPDW (6<<3) + +#define PTECACHABILITY PTENONCOHERWB /* loongson 2E only allows this */ + +#define PTEPID(n) (n) +#define PTEMAPMEM (1024*1024) +#define PTEPERTAB (PTEMAPMEM/BY2PG) +#define SEGMAPSIZE 512 +#define SSEGMAPSIZE 16 + +#define STLBLOG 15 +#define STLBSIZE (1< init.h + +reboot.h: initreboot.s rebootcode.c mem.h $objtype.s /$objtype/lib/libc.a + $AS $AFLAGS initreboot.s + $CC $CFLAGS rebootcode.c + # -lc is only for memmove + $LD -l -a -s -T$REBOOTADDR -R4 -o reboot.out initreboot.$O 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 + +l.$O: $objtype.s +main.$O: errstr.h init.h reboot.h +devether.$O: ../port/netif.h etherif.h +archln.$O clock.$O faultmips.$O mmu.$O trap.$O: /$objtype/include/ureg.h + +%.clean:V: + rm -f $stem.c [9bz]$stem [9bz]$stem.gz boot$stem.* *.list + +# override ../port/portmkfile +# create /boot/boot +boot$CONF.out: $CONF print.$O $BOOTDIR/boot.c $BOOTLIB + $BOOTDIR/mkboot $CONF > boot$CONF.c + $CC $CFLAGS boot$CONF.c + $CC $CFLAGS ../boot/printstub.c + $AS c_fcr0.s + $LD -a -o boot$CONF.out -T$UTZERO -R$MAXBY2PG boot$CONF.$O $BOOTLIB printstub.$O c_fcr0.$O > boot$CONF.list --- /dev/null +++ /sys/src/9/loongson/2f/pci.c @@ -0,0 +1,666 @@ +/* + * PCI support code. + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +typedef struct Pci Pci; + +struct Pci { + ulong id; + ulong cs; + ulong revclass; + ulong misc; /* cache line size, latency timer, header type, bist */ + ulong base[6]; /* base addr regs */ + ulong unused[5]; + ulong intr; + ulong mask[6]; + ulong trans[6]; +}; + +enum { + /* cs bits */ + CIoEn = (1<<0), + CMemEn = (1<<1), + CMasEn = (1<<2), + CSpcEn = (1<<4), + CParEn = (1<<6), + CSErrEn = (1<<8), + + SMasTgtAb = (1<<24), /* master target abort */ + SMasAb = (1<<25), /* master abort */ +}; + +enum { + MaxFNO = 7, + MaxUBN = 255, +}; + +enum +{ /* command register */ + IOen = (1<<0), + MEMen = (1<<1), + MASen = (1<<2), + MemWrInv = (1<<4), + PErrEn = (1<<6), + SErrEn = (1<<8), +}; + +static Lock pcicfglock; +static Lock pcicfginitlock; +static int pcicfgmode = -1; +static int pcimaxbno = 0; +static int pcimaxdno; +static Pcidev *pciroot; +static Pcidev *pcilist; +static Pcidev *pcitail; +static Pci *pci = (Pci*)PCICFG; + +static int pcicfgrw8(int, int, int, int); +static int pcicfgrw16(int, int, int, int); +static int pcicfgrw32(int, int, int, int); + +static char* bustypes[] = { + "CBUSI", + "CBUSII", + "EISA", + "FUTURE", + "INTERN", + "ISA", + "MBI", + "MBII", + "MCA", + "MPI", + "MPSA", + "NUBUS", + "PCI", + "PCMCIA", + "TC", + "VL", + "VME", + "XPRESS", +}; + +static int +tbdffmt(Fmt* fmt) +{ + char *p; + int l, r; + uint type, tbdf; + + if((p = malloc(READSTR)) == nil) + return fmtstrcpy(fmt, "(tbdfconv)"); + + switch(fmt->r){ + case 'T': + tbdf = va_arg(fmt->args, int); + if(tbdf == BUSUNKNOWN) + snprint(p, READSTR, "unknown"); + else{ + type = BUSTYPE(tbdf); + if(type < nelem(bustypes)) + l = snprint(p, READSTR, bustypes[type]); + else + l = snprint(p, READSTR, "%d", type); + snprint(p+l, READSTR-l, ".%d.%d.%d", + BUSBNO(tbdf), BUSDNO(tbdf), BUSFNO(tbdf)); + } + break; + + default: + snprint(p, READSTR, "(tbdfconv)"); + break; + } + r = fmtstrcpy(fmt, p); + free(p); + + return r; +} + +ulong +pcibarsize(Pcidev *p, int rno) +{ + ulong v, size; + + v = pcicfgrw32(p->tbdf, rno, 0, 1); + pcicfgrw32(p->tbdf, rno, 0xFFFFFFF0, 0); + size = pcicfgrw32(p->tbdf, rno, 0, 1); + if(v & 1) + size |= 0xFFFF0000; + pcicfgrw32(p->tbdf, rno, v, 0); + + return -(size & ~0x0F); +} + +static int +pcilscan(int bno, Pcidev** list) +{ + Pcidev *p, *head, *tail; + int dno, fno, i, hdt, l, maxfno, maxubn, rno, sbn, tbdf, ubn; + + maxubn = bno; + head = nil; + tail = nil; + /* dno from 5 due to its address mode */ + for(dno = 5; dno <= pcimaxdno; dno++){ + maxfno = 0; + for(fno = 0; fno <= maxfno; fno++){ + /* + * For this possible device, form the + * bus+device+function triplet needed to address it + * and try to read the vendor and device ID. + * If successful, allocate a device struct and + * start to fill it in with some useful information + * from the device's configuration space. + */ + tbdf = MKBUS(BusPCI, bno, dno, fno); + l = pcicfgrw32(tbdf, PciVID, 0, 1); + if(l == 0xFFFFFFFF || l == 0) + continue; + p = malloc(sizeof(*p)); + if(p == nil) + panic("pcilscan: no memory"); + p->tbdf = tbdf; + p->vid = l; + p->did = l>>16; + + if(pcilist != nil) + pcitail->list = p; + else + pcilist = p; + pcitail = p; + + p->pcr = pcicfgr16(p, PciPCR); + p->rid = pcicfgr8(p, PciRID); + p->ccrp = pcicfgr8(p, PciCCRp); + p->ccru = pcicfgr8(p, PciCCRu); + p->ccrb = pcicfgr8(p, PciCCRb); + p->cls = pcicfgr8(p, PciCLS); + p->ltr = pcicfgr8(p, PciLTR); + + p->intl = pcicfgr8(p, PciINTL); + p->intp = pcicfgr8(p, PciINTP); + + /* + * If the device is a multi-function device adjust the + * loop count so all possible functions are checked. + */ + hdt = pcicfgr8(p, PciHDT); + if(hdt & 0x80) + maxfno = MaxFNO; + + /* + * If appropriate, read the base address registers + * and work out the sizes. + */ + switch(p->ccrb) { + case 0x03: /* display controller */ + /* fall through */ + case 0x01: /* mass storage controller */ + case 0x02: /* network controller */ + case 0x04: /* multimedia device */ + case 0x07: /* simple comm. controllers */ + case 0x08: /* base system peripherals */ + case 0x09: /* input devices */ + case 0x0A: /* docking stations */ + case 0x0B: /* processors */ + case 0x0C: /* serial bus controllers */ + if((hdt & 0x7F) != 0) + break; + rno = PciBAR0 - 4; + for(i = 0; i < nelem(p->mem); i++) { + rno += 4; + p->mem[i].bar = pcicfgr32(p, rno); + p->mem[i].size = pcibarsize(p, rno); + } + break; + + case 0x00: + case 0x05: /* memory controller */ + case 0x06: /* bridge device */ + default: + break; + } + + if(head != nil) + tail->link = p; + else + head = p; + tail = p; + } + } + + *list = head; + for(p = head; p != nil; p = p->link){ + /* + * Find PCI-PCI bridges and recursively descend the tree. + */ + if(p->ccrb != 0x06 || p->ccru != 0x04) + continue; + + /* + * If the secondary or subordinate bus number is not + * initialised try to do what the PCI BIOS should have + * done and fill in the numbers as the tree is descended. + * On the way down the subordinate bus number is set to + * the maximum as it's not known how many buses are behind + * this one; the final value is set on the way back up. + */ + sbn = pcicfgr8(p, PciSBN); + ubn = pcicfgr8(p, PciUBN); + + if(sbn == 0 || ubn == 0) { + sbn = maxubn+1; + /* + * Make sure memory, I/O and master enables are + * off, set the primary, secondary and subordinate + * bus numbers and clear the secondary status before + * attempting to scan the secondary bus. + * + * Initialisation of the bridge should be done here. + */ + pcicfgw32(p, PciPCR, 0xFFFF0000); + l = (MaxUBN<<16)|(sbn<<8)|bno; + pcicfgw32(p, PciPBN, l); + pcicfgw16(p, PciSPSR, 0xFFFF); + maxubn = pcilscan(sbn, &p->bridge); + l = (maxubn<<16)|(sbn<<8)|bno; + + pcicfgw32(p, PciPBN, l); + } + else { + if(ubn > maxubn) + maxubn = ubn; + pcilscan(sbn, &p->bridge); + } + } + + return maxubn; +} + +static void +pcicfginit(void) +{ + char *p; + int n, bno; + Pcidev **list; + + lock(&pcicfginitlock); + if(pcicfgmode != -1) { + unlock(&pcicfginitlock); + return; + } + + pcicfgmode = 1; + pcimaxdno = 19; + + fmtinstall('T', tbdffmt); + + if(p = getconf("*pcimaxbno")){ + n = strtoul(p, 0, 0); + if(n < pcimaxbno) + pcimaxbno = n; + } + if(p = getconf("*pcimaxdno")){ + n = strtoul(p, 0, 0); + if(n < pcimaxdno) + pcimaxdno = n; + } + + list = &pciroot; + for(bno = 0; bno <= pcimaxbno; bno++){ + bno = pcilscan(bno, list); + while(*list) + list = &(*list)->link; + } + unlock(&pcicfginitlock); +} + +/* map the devince's cfg space and calculate the address */ +static void* +pcidevcfgaddr(int tbdf, int rno) +{ + ulong addr; + ulong b, d, f, type; + + b = BUSBNO(tbdf); + d = BUSDNO(tbdf); + f = BUSFNO(tbdf); + if(b == 0) { + /* Type 0 configuration on onboard PCI bus */ + addr = (1<<(d+11))|(f<<8)|rno; + type = 0x00000; + } else { + /* Type 1 configuration on offboard PCI bus */ + addr = (b<<16)|(d<<11)|(f<<8)|rno; + type = 0x10000; + } + + /* clear aborts */ + pci->cs |= SMasAb | SMasTgtAb; + + /* config map cfg reg to map the device's cfg space */ + *Pcimapcfg = (addr>>16)|type; + + return (void*)(PCIDEVCFG+(addr&0xffff)); +} + +static int +pcicfgrw8(int tbdf, int rno, int data, int read) +{ + int x; + void *addr; + + if(pcicfgmode == -1) + pcicfginit(); + + x = -1; + if(BUSDNO(tbdf) > pcimaxdno) + return x; + + lock(&pcicfglock); + addr = pcidevcfgaddr(tbdf, rno); + if(read) + x = *(uchar*)addr; + else + *(uchar*)addr = data; + unlock(&pcicfglock); + + return x; +} + +int +pcicfgr8(Pcidev* pcidev, int rno) +{ + return pcicfgrw8(pcidev->tbdf, rno, 0, 1); +} + +void +pcicfgw8(Pcidev* pcidev, int rno, int data) +{ + pcicfgrw8(pcidev->tbdf, rno, data, 0); +} + +static int +pcicfgrw16(int tbdf, int rno, int data, int read) +{ + int x; + void *addr; + + if(pcicfgmode == -1) + pcicfginit(); + + x = -1; + if(BUSDNO(tbdf) > pcimaxdno) + return x; + + lock(&pcicfglock); + addr = pcidevcfgaddr(tbdf, rno); + if(read) + x = *(ushort*)addr; + else + *(ushort*)addr = data; + unlock(&pcicfglock); + + return x; +} + +int +pcicfgr16(Pcidev* pcidev, int rno) +{ + return pcicfgrw16(pcidev->tbdf, rno, 0, 1); +} + +void +pcicfgw16(Pcidev* pcidev, int rno, int data) +{ + pcicfgrw16(pcidev->tbdf, rno, data, 0); +} + +static int +pcicfgrw32(int tbdf, int rno, int data, int read) +{ + int x; + void *addr; + + if(pcicfgmode == -1) + pcicfginit(); + + x = -1; + if(BUSDNO(tbdf) > pcimaxdno) + return x; + + lock(&pcicfglock); + addr = pcidevcfgaddr(tbdf, rno); + if(read) + x = *(ulong*)addr; + else + *(ulong*)addr = data; + unlock(&pcicfglock); + + return x; +} + +int +pcicfgr32(Pcidev* pcidev, int rno) +{ + return pcicfgrw32(pcidev->tbdf, rno, 0, 1); +} + +void +pcicfgw32(Pcidev* pcidev, int rno, int data) +{ + pcicfgrw32(pcidev->tbdf, rno, data, 0); +} + +Pcidev* +pcimatch(Pcidev* prev, int vid, int did) +{ + if(pcicfgmode == -1) + pcicfginit(); + + if(prev == nil) + prev = pcilist; + else + prev = prev->list; + + while(prev != nil){ + if((vid == 0 || prev->vid == vid) + && (did == 0 || prev->did == did)) + break; + prev = prev->list; + } + return prev; +} + +Pcidev* +pcimatchtbdf(int tbdf) +{ + Pcidev *pcidev; + + if(pcicfgmode == -1) + pcicfginit(); + + for(pcidev = pcilist; pcidev != nil; pcidev = pcidev->list) { + if(pcidev->tbdf == tbdf) + break; + } + return pcidev; +} + +void +pcireset(void) +{ + Pcidev *p; + + if(pcicfgmode == -1) + pcicfginit(); + + for(p = pcilist; p != nil; p = p->list) { + /* don't mess with the bridges */ + if(p->ccrb == 0x06) + continue; + pciclrbme(p); + } +} + +void +pcisetioe(Pcidev* p) +{ + p->pcr |= IOen; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pciclrioe(Pcidev* p) +{ + p->pcr &= ~IOen; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pcisetbme(Pcidev* p) +{ + p->pcr |= MASen; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pciclrbme(Pcidev* p) +{ + p->pcr &= ~MASen; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pcisetmwi(Pcidev* p) +{ + p->pcr |= MemWrInv; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pciclrmwi(Pcidev* p) +{ + p->pcr &= ~MemWrInv; + pcicfgw16(p, PciPCR, p->pcr); +} + +static int +pcigetpmrb(Pcidev* p) +{ + int ptr; + + if(p->pmrb != 0) + return p->pmrb; + p->pmrb = -1; + + /* + * If there are no extended capabilities implemented, + * (bit 4 in the status register) assume there's no standard + * power management method. + * Find the capabilities pointer based on PCI header type. + */ + if(!(pcicfgr16(p, PciPSR) & 0x0010)) + return -1; + switch(pcicfgr8(p, PciHDT)){ + default: + return -1; + case 0: /* all other */ + case 1: /* PCI to PCI bridge */ + ptr = 0x34; + break; + case 2: /* CardBus bridge */ + ptr = 0x14; + break; + } + ptr = pcicfgr32(p, ptr); + + while(ptr != 0){ + /* + * Check for validity. + * Can't be in standard header and must be double + * word aligned. + */ + if(ptr < 0x40 || (ptr & ~0xFC)) + return -1; + if(pcicfgr8(p, ptr) == 0x01){ + p->pmrb = ptr; + return ptr; + } + + ptr = pcicfgr8(p, ptr+1); + } + + return -1; +} + +int +pcigetpms(Pcidev* p) +{ + int pmcsr, ptr; + + if((ptr = pcigetpmrb(p)) == -1) + return -1; + + /* + * Power Management Register Block: + * offset 0: Capability ID + * 1: next item pointer + * 2: capabilities + * 4: control/status + * 6: bridge support extensions + * 7: data + */ + pmcsr = pcicfgr16(p, ptr+4); + + return pmcsr & 0x0003; +} + +int +pcisetpms(Pcidev* p, int state) +{ + int ostate, pmc, pmcsr, ptr; + + if((ptr = pcigetpmrb(p)) == -1) + return -1; + + pmc = pcicfgr16(p, ptr+2); + pmcsr = pcicfgr16(p, ptr+4); + ostate = pmcsr & 0x0003; + pmcsr &= ~0x0003; + + switch(state){ + default: + return -1; + case 0: + break; + case 1: + if(!(pmc & 0x0200)) + return -1; + break; + case 2: + if(!(pmc & 0x0400)) + return -1; + break; + case 3: + break; + } + pmcsr |= state; + pcicfgw16(p, ptr+4, pmcsr); + + return ostate; +} + +int +pcisubirq(int tbdf) +{ +// return Pciintrbase + pcicfgrw8(tbdf, PciINTP, 0, 1); + USED(tbdf); + return 5; // XXX rtl8139 +} --- /dev/null +++ /sys/src/9/loongson/2f/trap.c @@ -0,0 +1,1096 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "ureg.h" +#include "io.h" +#include +#include "../port/error.h" + +#define setstatus(v) /* experiment: delete this to enable recursive traps */ + +typedef struct Handler Handler; + +struct Handler { + void (*handler)(Ureg*, void*); + void *arg; + Handler *next; /* at this interrupt level */ + ulong intrs; +}; + +ulong offintrs; +ulong intrcauses[ILmax+1]; + +int intr(Ureg*); +void kernfault(Ureg*, int); +void noted(Ureg*, Ureg**, ulong); +void rfnote(Ureg**); + +char *excname[] = +{ + "trap: external interrupt", + "trap: TLB modification (store to unwritable)", + "trap: TLB miss (load or fetch)", + "trap: TLB miss (store)", + "trap: address error (load or fetch)", + "trap: address error (store)", + "trap: bus error (fetch)", + "trap: bus error (data load or store)", + "trap: system call", + "breakpoint", + "trap: reserved instruction", + "trap: coprocessor unusable", + "trap: arithmetic overflow", + "trap: TRAP exception", + "trap: VCE (instruction)", + "trap: floating-point exception", + "trap: coprocessor 2 implementation-specific", /* used as sys call for debugger */ + "trap: corextend unusable", + "trap: precise coprocessor 2 exception", + "trap: TLB read-inhibit", + "trap: TLB execute-inhibit", + "trap: undefined 21", + "trap: undefined 22", + "trap: WATCH exception", + "trap: machine checkcore", + "trap: undefined 25", + "trap: undefined 26", + "trap: undefined 27", + "trap: undefined 28", + "trap: undefined 29", + "trap: cache error", + "trap: VCE (data)", +}; + +char *fpcause[] = +{ + "inexact operation", + "underflow", + "overflow", + "division by zero", + "invalid operation", +}; + +struct { + char *name; + uint off; +} regname[] = { + "STATUS", Ureg_status, + "PC", Ureg_pc, + "SP", Ureg_sp, + "CAUSE",Ureg_cause, + "BADADDR", Ureg_badvaddr, + "TLBVIRT", Ureg_tlbvirt, + "HI", Ureg_hi, + "LO", Ureg_lo, + "R31", Ureg_r31, + "R30", Ureg_r30, + "R28", Ureg_r28, + "R27", Ureg_r27, + "R26", Ureg_r26, + "R25", Ureg_r25, + "R24", Ureg_r24, + "R23", Ureg_r23, + "R22", Ureg_r22, + "R21", Ureg_r21, + "R20", Ureg_r20, + "R19", Ureg_r19, + "R18", Ureg_r18, + "R17", Ureg_r17, + "R16", Ureg_r16, + "R15", Ureg_r15, + "R14", Ureg_r14, + "R13", Ureg_r13, + "R12", Ureg_r12, + "R11", Ureg_r11, + "R10", Ureg_r10, + "R9", Ureg_r9, + "R8", Ureg_r8, + "R7", Ureg_r7, + "R6", Ureg_r6, + "R5", Ureg_r5, + "R4", Ureg_r4, + "R3", Ureg_r3, + "R2", Ureg_r2, + "R1", Ureg_r1, +}; + +static Lock intrlock; +static Handler handlers[ILmax+1]; +static ulong pciintrmask; +static ulong i8259intrmask; + +static char * +ptlb(ulong phys) +{ + static char buf[4][32]; + static int k; + char *p; + + k = (k+1)&3; + p = buf[k]; + p += snprint(p, sizeof buf[k] - (p - buf[k]), "(%#lux %lud ", + (phys<<6) & ~(BY2PG-1), (phys>>3)&7); + if(phys & 4) + *p++ = 'd'; + if(phys & 2) + *p++ = 'v'; + if(phys & 1) + *p++ = 'g'; + *p++ = ')'; + *p = 0; + return buf[k]; +} + +static void +kpteprint(Ureg *ur) +{ + ulong i, tlbstuff[3]; + KMap *k; + + i = (ur->badvaddr & ~(2*BY2PG-1)) | TLBPID(tlbvirt()); + print("tlbvirt=%#lux\n", i); + i = gettlbp(i, tlbstuff); + print("i=%lud v=%#lux p0=%s p1=%s\n", + i, tlbstuff[0], ptlb(tlbstuff[1]), ptlb(tlbstuff[2])); + + i = (ur->badvaddr & ~KMAPADDR)>>15; + if(i > KPTESIZE){ + print("kpte index = %lud ?\n", i); + return; + } + k = &kpte[i]; + print("i=%lud, &k=%#p, k={v=%#lux, p0=%s, p1=%s, pg=%#p}\n", + i, k, k->virt, ptlb(k->phys0), ptlb(k->phys1), k->pg); + print("pg={pa=%#lux, va=%#lux}\n", k->pg->pa, k->pg->va); +} + +void +kvce(Ureg *ur, int ecode) +{ + char c; + Pte **p; + Page **pg; + Segment *s; + ulong addr, soff; + + c = 'D'; + if(ecode == CVCEI) + c = 'I'; + print("Trap: VCE%c: addr=%#lux\n", c, ur->badvaddr); + if((ur->badvaddr & KSEGM) == KSEG3) { + kpteprint(ur); + return; + } + if(up && !(ur->badvaddr & KSEGM)) { + addr = ur->badvaddr; + s = seg(up, addr, 0); + if(s == 0){ + print("kvce: no seg for %#lux\n", addr); + for(;;) + ; + } + addr &= ~(BY2PG-1); + soff = addr - s->base; + p = &s->map[soff/PTEMAPMEM]; + if(*p){ + pg = &(*p)->pages[(soff&(PTEMAPMEM-1))/BY2PG]; + if(*pg) + print("kvce: pa=%#lux, va=%#lux\n", + (*pg)->pa, (*pg)->va); + else + print("kvce: no *pg\n"); + }else + print("kvce: no *p\n"); + } +} + +/* prepare to go to user space */ +void +kexit(Ureg*) +{ + Tos *tos; + + /* precise time accounting, kernel exit */ + tos = (Tos*)(USTKTOP-sizeof(Tos)); + tos->kcycles += fastticks(&tos->cyclefreq) - up->kentry; + tos->pcycles = up->pcycles; + tos->pid = up->pid; +} + +char* +fpexcname(Ureg *ur, ulong fcr31, char *buf, uint size) +{ + int i; + char *s; + ulong fppc; + + fppc = ur->pc; + if(ur->cause & BD) /* branch delay */ + fppc += 4; + s = 0; + if(fcr31 & FPUNIMP) + s = "unimplemented operation"; + else { + fcr31 >>= 7; /* trap enable bits */ + fcr31 &= (fcr31>>5); /* anded with exceptions */ + for(i=0; i<5; i++) + if(fcr31 & (1<cause>>2)&EXCMASK; + user = ur->status&KUSER; + if (ur->cause & TS) + panic("trap: tlb shutdown"); + + fpchk = 0; + if(user){ + up->dbgreg = ur; + cycles(&up->kentry); + if(up && up->fpstate == FPactive) { + if((ur->status & CU1) == 0) + panic("FPactive but no CU1"); + ur->status &= ~CU1; + up->fpstate = FPinactive; + savefpregs(&up->fpsave); + } + } + + if (up && (char *)(ur) - up->kstack < 1024 && dumps++ == 0) { + iprint("trap: proc %ld kernel stack getting full\n", up->pid); + dumpregs(ur); + dumpstack(); + } + if (up == nil && + (char *)(ur) - (char *)m->stack < 1024 && dumps++ == 0) { + iprint("trap: cpu%d kernel stack getting full\n", m->machno); + dumpregs(ur); + dumpstack(); + } + +// splhi(); /* for the experiment: make it explicit */ + /* clear EXL in status */ + setstatus(getstatus() & ~EXL); + + clockintr = 0; + switch(ecode){ + case CINT: + clockintr = intr(ur); + break; + + case CFPE: + fptrap(ur); + clrfpintr(); + fpchk = 1; + break; + + case CTLBM: + case CTLBL: + case CTLBS: + /* user tlb entries assumed not overwritten during startup */ + if(up == 0) + kernfault(ur, ecode); + + if(!user && (ur->badvaddr & KSEGM) == KSEG3) { + kfault(ur); + break; + } + x = up->insyscall; + up->insyscall = 1; + spllo(); + faultmips(ur, user, ecode); + up->insyscall = x; + break; + + case CVCEI: + case CVCED: + kvce(ur, ecode); + goto Default; + + case CWATCH: + if(!user) + panic("watchpoint trap from kernel mode pc=%#p", + ur->pc); + //fpwatch(ur); XXX + break; + + case CCPU: + cop = (ur->cause>>28)&3; + if(user && up && cop == 1) { + if(up->fpstate & FPillegal) { + /* someone used floating point in a note handler */ + postnote(up, 1, + "sys: floating point in note handler", + NDebug); + break; + } + if(up->fpstate == FPinit) { + up->fpstate = FPinactive; + fpfcr31 = up->fpsave.fpstatus; + up->fpsave = initfp; + up->fpsave.fpstatus = fpfcr31; + break; + } + if(up->fpstate == FPinactive) + break; + } + /* Fallthrough */ + + Default: + default: + if(user) { + spllo(); + snprint(buf, sizeof buf, "sys: %s", excname[ecode]); + postnote(up, 1, buf, NDebug); + break; + } + if (ecode == CADREL || ecode == CADRES) + iprint("kernel addr exception for va %#p pid %#ld %s\n", + ur->badvaddr, (up? up->pid: 0), + (up? up->text: "")); + print("cpu%d: kernel %s pc=%#lux\n", + m->machno, excname[ecode], ur->pc); + dumpregs(ur); + dumpstack(); + if(m->machno == 0) + spllo(); + exit(1); + } + + if(fpchk) { + fpfcr31 = up->fpsave.fpstatus; + if((fpfcr31>>12) & ((fpfcr31>>7)|0x20) & 0x3f) { + spllo(); + fpexcep = fpexcname(ur, fpfcr31, buf1, sizeof buf1); + snprint(buf, sizeof buf, "sys: fp: %s", fpexcep); + postnote(up, 1, buf, NDebug); + } + } + + splhi(); + + /* delaysched set because we held a lock or because our quantum ended */ + if(up && up->delaysched && clockintr){ + sched(); + splhi(); + } + + if(user){ + notify(ur); + if(up->fpstate == FPinactive) { + restfpregs(&up->fpsave, up->fpsave.fpstatus&~FPEXCMASK); + up->fpstate = FPactive; + ur->status |= CU1; + } + kexit(ur); + } + + /* restore EXL in status */ + setstatus(getstatus() | EXL); +} + +/* periodically zero all the interrupt counts */ +static void +resetcounts(void) +{ + int i; + Handler *hp; + + ilock(&intrlock); + for (i = 0; i < nelem(handlers); i++) + for (hp = &handlers[i]; hp != nil; hp = hp->next) + hp->intrs = 0; + iunlock(&intrlock); +} + +/* + * set handlers + */ +void +intrenable(int irq, void (*h)(Ureg*, void*), void *arg, int subirq) +{ + Handler *hp; + static int resetclock; + + if (h == nil) + panic("intrenable: nil handler intr %d", irq); + if(irq < ILmin || irq >= nelem(handlers)) + panic("intrenable: bad handler intr %d %#p", irq, h); + + hp = &handlers[irq]; + ilock(&intrlock); + if (hp->handler != nil) { /* occupied? */ + /* add a new one at the end of the chain */ + for (; hp->next != nil; hp = hp->next) + ; + hp->next = smalloc(sizeof *hp); + hp = hp->next; + hp->next = nil; + } + hp->handler = h; + hp->arg = arg; + iunlock(&intrlock); + + if (irq == ILpci) { // enable pci sub-interrupt + *Pciintrsts = 0; + *Pciintrenset = 1 << subirq; + coherence(); + pciintrmask |= 1 << subirq; + } + + if (irq == IL8259) + { + i8259enable(subirq); + i8259intrmask |= 1 << subirq; + } + + intron(1 << (ILshift + irq)); + if (!resetclock) { + resetclock = 1; + addclock0link(resetcounts, 100); + } +} + +void +intrshutdown(void) +{ + introff(INTMASK); +} + +static void +jabberoff(Ureg *ur, int irq, ulong bit) +{ + introff(bit); /* interrupt off now ... */ + if (ur) + ur->status &= ~bit; /* ... and upon return */ + offintrs |= bit; + iprint("irq %d jabbering; shutting it down\n", irq); +} + +ulong +pollall(Ureg *ur, ulong cause) /* must be called splhi */ +{ + int i, intrs, sts, subirq; + ulong bit; + Handler *hp; + + /* exclude clock and sw intrs */ + intrs = cause & (INTR6|INTR5|INTR4|INTR3|INTR2) & getstatus(); + if(intrs == 0) + return cause; + + ilock(&intrlock); + for (i = ILmax; i >= ILmin; i--) { + bit = 1 << (ILshift + i); + if (!(intrs & bit)) + continue; + intrcauses[i]++; + for (hp = &handlers[i]; hp != nil; hp = hp->next) + if (hp->handler) { + if (i == ILpci) { + sts = *Pciintrsts & *Pciintren; + if((sts & pciintrmask) == 0) + continue; + // XXX need to clear sub-intr bits ? + //*Pciintrsts &= ~(1 << Pciintrether); + *Pciintrsts = 0; + } + + if (i == IL8259) { + subirq = i8259intack(); + if(subirq < 0 || (1 << subirq) & i8259intrmask == 0) + continue; + i8259isr(subirq); + } + + (*hp->handler)(ur, hp->arg); + splhi(); + if (++hp->intrs > 25000) { + jabberoff(ur, i, bit); + intrs &= ~bit; + hp->intrs = 0; + } + } else if (ur) + iprint("no handler for interrupt %d\n", i); + cause &= ~bit; + } + iunlock(&intrlock); + return cause; +} + +int +intr(Ureg *ur) +{ + int clockintr; + ulong cause; + + m->intr++; + clockintr = 0; + /* + * ignore interrupts that we have disabled, even if their cause bits + * are set. + */ + cause = ur->cause & ur->status & INTMASK; + cause &= ~(INTR1|INTR0); /* ignore sw interrupts */ +// if (cause == 0) +// print("spurious interrupt %lux %lux\n", ur->cause, ur->status); + if(cause & INTR7){ + clock(ur); + intrcauses[ILclock]++; + cause &= ~(1 << (ILclock + ILshift)); + clockintr = 1; + } + cause = pollall(ur, cause); + if(cause){ + print("intr: cause %#lux not handled\n", cause); + exit(1); + } + + /* preemptive scheduling */ + if(up && !clockintr) + preempted(); + /* if it was a clockintr, sched will be called at end of trap() */ + return clockintr; +} + +void +kernfault(Ureg *ur, int code) +{ + print("panic: kfault %s badvaddr=%#lux", excname[code], ur->badvaddr); + kpteprint(ur); + print("u=%#p status=%#lux pc=%#lux sp=%#lux\n", + up, ur->status, ur->pc, ur->sp); + delay(500); + panic("kfault"); +} + +static void +getpcsp(ulong *pc, ulong *sp) +{ + *pc = getcallerpc(&pc); + *sp = (ulong)&pc-4; +} + +void +callwithureg(void (*fn)(Ureg*)) +{ + Ureg ureg; + + memset(&ureg, 0, sizeof ureg); + getpcsp((ulong*)&ureg.pc, (ulong*)&ureg.sp); + ureg.r31 = getcallerpc(&fn); + fn(&ureg); +} + +static void +_dumpstack(Ureg *ureg) +{ + ulong l, v, top, i; + extern ulong etext; + + if(up == 0) + return; + + print("ktrace /kernel/path %.8lux %.8lux %.8lux\n", + ureg->pc, ureg->sp, ureg->r31); + top = (ulong)up->kstack + KSTACK; + i = 0; + for(l=ureg->sp; l < top; l += BY2WD) { + v = *(ulong*)l; + if(KTZERO < v && v < (ulong)&etext) { + print("%.8lux=%.8lux ", l, v); + if((++i%4) == 0){ + print("\n"); + delay(200); + } + } + } + print("\n"); +} + +void +dumpstack(void) +{ + callwithureg(_dumpstack); +} + +static ulong +R(Ureg *ur, int i) +{ + uchar *s; + + s = (uchar*)ur; + return *(ulong*)(s + regname[i].off - Uoffset); +} + +void +dumpregs(Ureg *ur) +{ + int i; + + if(up) + print("registers for %s %lud\n", up->text, up->pid); + else + print("registers for kernel\n"); + + for(i = 0; i < nelem(regname); i += 2) + print("%s\t%#.8lux\t%s\t%#.8lux\n", + regname[i].name, R(ur, i), + regname[i+1].name, R(ur, i+1)); +} + +int +notify(Ureg *ur) +{ + int l, s; + ulong sp; + Note *n; + + if(up->procctl) + procctl(up); + if(up->nnote == 0) + return 0; + + s = spllo(); + qlock(&up->debug); + up->fpstate |= FPillegal; + up->notepending = 0; + n = &up->note[0]; + if(strncmp(n->msg, "sys:", 4) == 0) { + l = strlen(n->msg); + if(l > ERRMAX-15) /* " pc=0x12345678\0" */ + l = ERRMAX-15; + + seprint(n->msg+l, &n->msg[sizeof n->msg], " pc=%#lux", ur->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); + splx(s); + return 0; + } + + if(!up->notify) { + qunlock(&up->debug); + pexit(n->msg, n->flag!=NDebug); + } + sp = ur->usp - sizeof(Ureg) - BY2WD; /* libc adds 4 to usp */ + + if(!okaddr((ulong)up->notify, BY2WD, 0) || + !okaddr(sp-ERRMAX-4*BY2WD, sizeof(Ureg)+ERRMAX+4*BY2WD, 1)) { + pprint("suicide: bad address or sp in notify\n"); + qunlock(&up->debug); + pexit("Suicide", 0); + } + + memmove((Ureg*)sp, ur, sizeof(Ureg)); /* push user regs */ + *(Ureg**)(sp-BY2WD) = up->ureg; /* word under Ureg is old up->ureg */ + up->ureg = (void*)sp; + + sp -= BY2WD+ERRMAX; + memmove((char*)sp, up->note[0].msg, ERRMAX); /* push err string */ + + sp -= 3*BY2WD; + *(ulong*)(sp+2*BY2WD) = sp+3*BY2WD; /* arg 2 is string */ + ur->r1 = (long)up->ureg; /* arg 1 is ureg* */ + ((ulong*)sp)[1] = (ulong)up->ureg; /* arg 1 0(FP) is ureg* */ + ((ulong*)sp)[0] = 0; /* arg 0 is pc */ + ur->usp = sp; + /* + * arrange to resume at user's handler as if handler(ureg, errstr) + * were being called. + */ + ur->pc = (ulong)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; +} + +/* + * Check that status is OK to return from note. + */ +int +validstatus(ulong kstatus, ulong ustatus) +{ +// if((kstatus & (INTMASK|KX|SX|UX)) != (ustatus & (INTMASK|KX|SX|UX))) + if((kstatus & INTMASK) != (ustatus & INTMASK)) + return 0; + if((ustatus&(KSU|ERL|EXL|IE)) != (KUSER|EXL|IE)) + return 0; + if(ustatus & (0xFFFF0000&~CU1)) /* no CU3, CU2, CU0, RP, FR, RE, DS */ + return 0; + return 1; +} + +/* + * Return user to state before notify(); called from user's handler. + */ +void +noted(Ureg *kur, Ureg **urp, ulong arg0) +{ + Ureg *nur; + ulong oureg, sp; + + 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; + + up->fpstate &= ~FPillegal; + + nur = up->ureg; + + oureg = (ulong)nur; + if((oureg & (BY2WD-1)) + || !okaddr((ulong)oureg-BY2WD, BY2WD+sizeof(Ureg), 0)){ + pprint("bad up->ureg in noted or call to noted() when not notified\n"); + qunlock(&up->debug); + pexit("Suicide", 0); + } + + if(!validstatus(kur->status, nur->status)) { + qunlock(&up->debug); + pprint("bad noted ureg status %#lux\n", nur->status); + pexit("Suicide", 0); + } + + memmove(*urp, up->ureg, sizeof(Ureg)); + switch(arg0) { + case NCONT: + case NRSTR: /* only used by APE */ + if(!okaddr(nur->pc, BY2WD, 0) || !okaddr(nur->usp, BY2WD, 0)){ + pprint("suicide: trap in noted\n"); + qunlock(&up->debug); + pexit("Suicide", 0); + } + up->ureg = (Ureg*)(*(ulong*)(oureg-BY2WD)); + qunlock(&up->debug); + splhi(); + /* + * the old challenge and carrera ports called rfnote here, + * but newer ports do not, and notes seem to work only + * without this call. + */ + // rfnote(urp); /* return from note with SP=urp */ + break; + + case NSAVE: /* only used by APE */ + if(!okaddr(nur->pc, BY2WD, 0) || !okaddr(nur->usp, BY2WD, 0)){ + pprint("suicide: trap in noted\n"); + qunlock(&up->debug); + pexit("Suicide", 0); + } + qunlock(&up->debug); + sp = oureg-4*BY2WD-ERRMAX; + + splhi(); + (*urp)->sp = sp; + ((ulong*)sp)[1] = oureg; /* arg 1 0(FP) is ureg* */ + ((ulong*)sp)[0] = 0; /* arg 0 is pc */ + (*urp)->r1 = oureg; /* arg 1 is ureg* */ + + // rfnote(urp); /* return from note with SP=urp */ + break; + + default: + pprint("unknown noted arg %#lux\n", arg0); + up->lastnote.flag = NDebug; + /* fall through */ + + case NDFLT: + if(up->lastnote.flag == NDebug) + pprint("suicide: %s\n", up->lastnote.msg); + qunlock(&up->debug); + pexit(up->lastnote.msg, up->lastnote.flag!=NDebug); + } +} + +#include "../port/systab.h" + +static Ref goodsyscall; +static Ref totalsyscall; + +static void +sctracesetup(ulong scallnr, ulong sp, uintptr pc, vlong *startnsp) +{ + 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))) + validaddr(sp, sizeof(Sargs), 0); + + syscallfmt(scallnr, pc, (va_list)sp); + up->procctl = Proc_stopme; + procctl(up); + if(up->syscalltrace) + free(up->syscalltrace); + up->syscalltrace = nil; + *startnsp = todget(nil); + } +} + +static void +sctracefinish(ulong scallnr, ulong sp, int ret, vlong startns) +{ + int s; + + if(up->procctl == Proc_tracesyscall){ + up->procctl = Proc_stopme; + sysretfmt(scallnr, (va_list)sp, ret, + startns, todget(nil)); + s = splhi(); + procctl(up); + splx(s); + if(up->syscalltrace) + free(up->syscalltrace); + up->syscalltrace = nil; + } +} + +/* + * called directly from assembler, not via trap() + */ +long +syscall(Ureg *aur) +{ + int i; + volatile long ret; + ulong sp, scallnr; + vlong startns; + char *e; + Ureg *ur; + + cycles(&up->kentry); + + incref(&totalsyscall); + m->syscall++; + up->insyscall = 1; + ur = aur; + up->pc = ur->pc; + up->dbgreg = aur; + ur->cause = 16<<2; /* for debugging: system call is undef 16 */ + + scallnr = ur->r1; + up->scallnr = ur->r1; + sp = ur->sp; + sctracesetup(scallnr, sp, ur->pc, &startns); + + /* clear EXL in status */ + setstatus(getstatus() & ~EXL); + + if(up->fpstate == FPactive) { + if((ur->status & CU1) == 0) + panic("syscall: FPactive but no CU1"); + up->fpsave.fpstatus = fcr31(); + up->fpstate = FPinit; + ur->status &= ~CU1; + } + spllo(); + + up->nerrlab = 0; + ret = -1; + if(!waserror()) { + if(scallnr >= nsyscall || systab[scallnr] == 0){ + pprint("bad sys call number %ld pc %#lux\n", + scallnr, ur->pc); + postnote(up, 1, "sys: bad sys call", NDebug); + error(Ebadarg); + } + + if(sp & (BY2WD-1)){ + pprint("odd sp in sys call pc %#lux sp %#lux\n", + ur->pc, ur->sp); + postnote(up, 1, "sys: odd stack", NDebug); + error(Ebadarg); + } + + if(sp<(USTKTOP-BY2PG) || sp>(USTKTOP-sizeof(Sargs))) + validaddr(sp, sizeof(Sargs), 0); + + up->s = *(Sargs*)sp; /* spim's libc is different to mips ... */ + up->psstate = sysctab[scallnr]; + + 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 [%lud]: %d extra\n", scallnr, up->nerrlab); + for(i = 0; i < NERR; i++) + print("sp=%#lux pc=%#lux\n", + up->errlab[i].sp, up->errlab[i].pc); + panic("error stack"); + } + sctracefinish(scallnr, sp, ret, startns); + + ur->pc += 4; + ur->r1 = ret; + + up->psstate = 0; + up->insyscall = 0; + + if(scallnr == NOTED) { /* ugly hack */ + noted(ur, &aur, *(ulong*)sp); /* may return */ + ret = ur->r1; + } + incref(&goodsyscall); + splhi(); + if(scallnr!=RFORK && (up->procctl || up->nnote)){ + ur->r1 = ret; /* load up for noted() above */ + notify(ur); + } + /* if we delayed sched because we held a lock, sched now */ + if(up->delaysched) + sched(); + kexit(ur); + + /* restore EXL in status */ + setstatus(getstatus() | EXL); + + return ret; +} + +void +forkchild(Proc *p, Ureg *ur) +{ + Ureg *cur; + + p->sched.sp = (ulong)p->kstack+KSTACK-UREGSIZE; + p->sched.pc = (ulong)forkret; + + cur = (Ureg*)(p->sched.sp+2*BY2WD); + memmove(cur, ur, sizeof(Ureg)); + + cur->pc += 4; + + /* Things from bottom of syscall we never got to execute */ + p->psstate = 0; + p->insyscall = 0; +} + +static void +linkproc(void) +{ + spllo(); + up->kpfun(up->kparg); + pexit("kproc exiting", 0); +} + +void +kprocchild(Proc *p, void (*func)(void*), void *arg) +{ + p->sched.pc = (ulong)linkproc; + p->sched.sp = (ulong)p->kstack+KSTACK; + + p->kpfun = func; + p->kparg = arg; +} + +/* set up user registers before return from exec() */ +long +execregs(ulong entry, ulong ssize, ulong nargs) +{ + Ureg *ur; + ulong *sp; + + sp = (ulong*)(USTKTOP - ssize); + *--sp = nargs; + + ur = (Ureg*)up->dbgreg; + ur->usp = (ulong)sp; + ur->pc = entry - 4; /* syscall advances it */ + up->fpsave.fpstatus = initfp.fpstatus; + return USTKTOP-sizeof(Tos); /* address of kernel/user shared data */ +} + +ulong +userpc(void) +{ + Ureg *ur; + + ur = (Ureg*)up->dbgreg; + return ur->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 *xp, char *pureg, char *uva, int n) +{ + ulong status; + + status = xp->status; + memmove(pureg, uva, n); + xp->status = status; +} + +/* + * Give enough context in the ureg to produce a kernel stack for + * a sleeping process + */ +void +setkernur(Ureg *xp, Proc *p) +{ + xp->pc = p->sched.pc; + xp->sp = p->sched.sp; + xp->r24 = (ulong)p; /* up */ + xp->r31 = (ulong)sched; +} + +ulong +dbgpc(Proc *p) +{ + Ureg *ur; + + ur = p->dbgreg; + if(ur == 0) + return 0; + + return ur->pc; +} --- /dev/null +++ /sys/src/9/loongson/archln.c @@ -0,0 +1,85 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" +#include "ureg.h" +#include "../port/netif.h" +#include "etherif.h" + +int +archether(unsigned ctlrno, Ether *ether) +{ + switch(ctlrno) { + case 0: + ether->type = "rtl8139"; + ether->ctlrno = ctlrno; + ether->irq = ILpci; + ether->nopt = 0; + ether->mbps = 100; + return 1; + } + return -1; +} + +/* port i/o */ +void +insb(int port, void *p, int count) +{ + uchar *q = p; + + for(; count > 0; count--) + *q++ = inb(port); +} + +void +inss(int port, void *p, int count) +{ + ushort *q = p; + + for(; count > 0; count--) + *q++ = ins(port); +} + +void +insl(int port, void *p, int count) +{ + ulong *q = p; + + for(; count > 0; count--) + *q++ = inl(port); +} + +void +outsb(int port, void *p, int count) +{ + uchar *q = p; + + for(; count > 0; count--) + outb(port, *q++); +} + +void +outss(int port, void *p, int count) +{ + ushort *q = p; + + for(; count > 0; count--) + outs(port, *q++); +} + +void +outsl(int port, void *p, int count) +{ + ulong *q = p; + + for(; count > 0; count--) + outl(port, *q++); +} + +void +fptrap(Ureg*) +{ +} --- /dev/null +++ /sys/src/9/loongson/c_fcr0.s @@ -0,0 +1,3 @@ +TEXT C_fcr0(SB), $-4 + MOVW $0x500, R1 /* claim to be an r4k, thus have ll/sc */ + RET --- /dev/null +++ /sys/src/9/loongson/clock.c @@ -0,0 +1,259 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +#include "ureg.h" + +enum { + Cyccntres = 2, /* counter advances at ½ clock rate */ + Basetickfreq = 667*Mhz / Cyccntres, +}; + +enum { + Instrs = 10*Mhz, +}; + +ulong cpufreq; +ulong basetickfreq; + +/* + * delay for l milliseconds more or less. + */ +void +delay(int l) +{ + while(l-- > 0) + microdelay(1000); +} + +/* + * microseconds delay + */ +void +microdelay(int l) +{ + int s; + ulong x, cyc, cnt, speed; + + speed = m->speed; + if (speed == 0) + speed = cpufreq / Mhz; + cyc = (ulong)l * (speed / Cyccntres); + s = splhi(); + cnt = rdcount(); + x = cnt + cyc; + if (x < cnt || x >= ~0ul - cpufreq/Cyccntres) { + /* counter will wrap between now and x, or x is too near ~0 */ + wrcount(0); /* somewhat drastic */ + wrcompare(rdcompare() - cnt); /* match new count */ + x = cyc; + } + while(rdcount() < x) + ; + splx(s); +} + +void +clock(Ureg *ureg) +{ + wrcompare(rdcount()+m->maxperiod); /* side-effect: dismiss intr */ + timerintr(ureg, 0); +} + +static long +issue1loop(void) +{ + register regint i; + long st; + + i = Instrs; + st = perfticks(); + do { + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; --i; --i; --i; --i; --i; + --i; --i; --i; --i; --i; + /* omit 3 (--i) to account for conditional branch, nop & jump */ + i -= 1+3; /* --i plus 3 omitted (--i) instructions */ + } while(--i >= 0); + return perfticks() - st; +} + +/* estimate instructions/s. */ +static int +guessmips(long (*loop)(void), char *) +{ + int s; + long cyc; + + do { + s = splhi(); + cyc = loop(); + splx(s); + if (cyc < 0) + iprint("again..."); + } while (cyc < 0); + /* + * Instrs instructions took cyc cycles @ basetickfreq Hz. + * round the result. + */ + return (((vlong)basetickfreq * Instrs) / cyc + Mhz/2) / Mhz; +} + +void +clockinit(void) +{ + int mips; + + /* + * calibrate fastclock + */ + if(cpufreq != 0) + basetickfreq = cpufreq / Cyccntres; + else + basetickfreq = Basetickfreq; /* sanity */ + mips = guessmips(issue1loop, "single"); + + /* + * m->delayloop should be the number of delay loop iterations + * needed to consume 1 ms, assuming 2 instr'ns in the delay loop. + */ + m->delayloop = mips*Mhz / (1000 * 2); + if(m->delayloop == 0) + m->delayloop = 1; + + m->speed = mips; + m->hz = m->speed*Mhz; + + m->maxperiod = basetickfreq / HZ; + m->minperiod = basetickfreq / (100*HZ); + wrcompare(rdcount()+m->maxperiod); + + /* + * desynchronize the processor clocks so that they all don't + * try to resched at the same time. + */ + delay(m->machno*2); + + syncclock(); + intron(INTR7); +} + +/* + * Tval is supposed to be in fastticks units. + * One fasttick unit is 1/Basetickfreq seconds. + */ +void +timerset(Tval next) +{ + int x; + long period; + + if(next == 0) + return; + x = splhi(); /* don't let us get scheduled */ + period = next - fastticks(nil); + if(period > m->maxperiod - m->minperiod) + period = m->maxperiod; + else if(period < m->minperiod) + period = m->minperiod; + wrcompare(rdcount()+period); + splx(x); +} + +/* + * The rewriting of compare in this routine shouldn't be necessary. + * However, we lose clock interrupts if I don't, either a chip bug + * or one of ours -- presotto + */ +uvlong +fastticks(uvlong *hz) +{ + int x; + ulong delta, count; + + if(hz) + *hz = basetickfreq; + + /* avoid reentry on interrupt or trap, to prevent recursion */ + x = splhi(); + count = rdcount(); + if(rdcompare() - count > m->maxperiod) + wrcompare(count+m->maxperiod); + + if (count < m->lastcount) /* wrapped around? */ + delta = count + ((1ull<<32) - m->lastcount); + else + delta = count - m->lastcount; + m->lastcount = count; + m->fastticks += delta; + splx(x); + return m->fastticks; +} + +ulong +µs(void) +{ + return fastticks2us(fastticks(nil)); +} + +/* + * performance measurement ticks. must be low overhead. + * doesn't have to count over a second. + */ +ulong +perfticks(void) +{ + return rdcount(); +} + +long +lcycles(void) +{ + return perfticks(); +} + +/* should use vlong hw counters ideally; lcycles is inadequate */ +void +cycles(uvlong *cycp) +{ + *cycp = fastticks(nil); +} + +Lock mpsynclock; + +/* + * synchronize all clocks with processor 0 + */ +void +syncclock(void) +{ + uvlong x; + + if(m->machno == 0){ + m->lastcount = rdcount(); + m->fastticks = 0; + m->ticks = 0; + wrcompare(rdcount()+m->maxperiod); + } else { + /* wait for processor 0's soft clock to change and then sync ours */ + lock(&mpsynclock); + x = MACHP(0)->fastticks; + while(MACHP(0)->fastticks == x) + ; + m->lastcount = rdcount(); + m->fastticks = MACHP(0)->fastticks; + m->ticks = MACHP(0)->ticks; + wrcompare(rdcount()+m->maxperiod); + unlock(&mpsynclock); + } +} --- /dev/null +++ /sys/src/9/loongson/dat.h @@ -0,0 +1,259 @@ +typedef struct Conf Conf; +typedef struct Confmem Confmem; +typedef struct FPsave FPsave; +typedef struct ISAConf ISAConf; +typedef struct KMap KMap; +typedef struct Lance Lance; +typedef struct Lancemem Lancemem; +typedef struct Label Label; +typedef struct Lock Lock; +typedef struct Mach Mach; +typedef struct MMU MMU; +typedef struct Notsave Notsave; +typedef struct Pcidev Pcidev; +typedef struct PMMU PMMU; +typedef struct Softtlb Softtlb; +typedef struct Ureg Ureg; +typedef struct Proc Proc; +typedef uvlong Tval; + +#pragma incomplete Pcidev + +#define MAXSYSARG 5 /* for mount(fd, afd, mpt, flag, arg) */ + +/* + * parameters for sysproc.c and rebootcmd.c + */ +#define AOUT_MAGIC N_MAGIC || magic==P_MAGIC +/* r3k or r4k boot images */ +#define BOOT_MAGIC (0x160<<16) || magic == ((0x160<<16)|3) + +/* + * machine dependent definitions used by ../port/dat.h + */ + +struct Lock +{ + ulong key; /* semaphore (non-zero = locked) */ + ulong sr; + ulong pc; + Proc *p; + Mach *m; + ushort isilock; +}; + +struct Label +{ + ulong sp; + ulong pc; +}; + +struct Confmem +{ + ulong base; + ulong npage; + ulong kbase; + ulong klimit; +}; + +struct Conf +{ + ulong nmach; /* processors */ + ulong nproc; /* processes */ + Confmem mem[2]; /* physical memory */ + ulong npage; /* total physical pages of memory */ + ulong upages; /* user page pool */ + ulong nimage; /* number of page cache image headers */ + ulong nswap; /* number of swap pages */ + int nswppo; /* max # of pageouts per segment pass */ + ulong copymode; /* 0 is copy on write, 1 is copy on reference */ + ulong ialloc; /* bytes available for interrupt-time allocation */ + ulong pipeqsize; /* size in bytes of pipe queues */ + int nuart; /* number of uart devices */ + int monitor; /* has monitor? */ +}; + +/* + * floating point registers + */ +enum +{ + /* floating point state */ + FPinit, + FPactive, + FPinactive, + FPemu, + + /* bit meaning floating point illegal */ + FPillegal= 0x100, +}; + +enum { + Nfpregs = 32, /* floats; half as many doubles */ +}; + +/* + * floating point + * fpstate is separate, kept in Proc + */ +struct FPsave +{ + /* /dev/proc expects the registers to be first in FPsave */ + ulong reg[Nfpregs]; /* the canonical bits */ + union { + ulong fpstatus; /* both are fcr31 */ + ulong fpcontrol; + }; + + int fpdelayexec; /* executing delay slot of branch */ + uintptr fpdelaypc; /* pc to resume at after */ + ulong fpdelaysts; /* save across user-mode delay-slot execution */ + + /* stuck-fault detection */ + uintptr fppc; /* addr of last fault */ + int fpcnt; /* how many consecutive at that addr */ +}; + +/* + * mmu goo in the Proc structure + */ +struct PMMU +{ + int pidonmach[MAXMACH]; +}; + +/* + * things saved in the Proc structure during a notify + */ +struct Notsave +{ + ulong nonempty; +}; + +#include "../port/portdat.h" + +/* First FIVE members' offsets known by l.s */ +struct Mach +{ + /* the following are all known by l.s and cannot be moved */ + int machno; /* physical id of processor FIRST */ + Softtlb*stb; /* Software tlb simulation SECOND */ + Proc* proc; /* process on this processor THIRD */ + ulong splpc; /* pc that called splhi() FOURTH */ + ulong tlbfault; /* # of tlb faults FIFTH */ + ulong ktlbfault; + ulong utlbfault; + + /* the following is safe to move */ + ulong tlbpurge; + ulong ticks; /* of the clock since boot time */ + Label sched; /* scheduler wakeup */ + void* alarm; /* alarms bound to this clock */ + int lastpid; /* last pid allocated on this machine */ + Proc* pidproc[NTLBPID]; /* proc that owns tlbpid on this mach */ + KMap* kactive; /* active on this machine */ + int knext; + uchar ktlbx[NTLB]; /* tlb index used for kmap */ + uchar ktlbnext; + int speed; /* cpu speed */ + ulong delayloop; /* for the delay() routine */ + ulong fairness; /* for runproc */ + int flushmmu; + int inclockintr; + int ilockdepth; + Perf perf; /* performance counters */ + uvlong cyclefreq; /* Frequency of user readable cycle counter */ + + /* for per-processor timers */ + ulong lastcount; + uvlong fastticks; + ulong hz; + ulong maxperiod; + ulong minperiod; + + Proc* readied; /* for runproc */ + ulong schedticks; /* next forced context switch */ + + int pfault; + int cs; + int syscall; + int load; + int intr; + int hashcoll; /* soft-tlb hash collisions */ + int paststartup; /* for putktlb */ + + int stack[1]; +}; + +struct KMap +{ + Ref; + ulong virt; + ulong phys0; + ulong phys1; + KMap* next; + KMap* konmach[MAXMACH]; + Page* pg; + ulong pc; /* of caller to kmap() */ +}; + +#define VA(k) ((k)->virt) +#define PPN(x) ((ulong)(x)>>6) /* PPN in TLBPHYS0-1 */ + +/* offsets known by l.s */ +struct Softtlb +{ + ulong virt; + ulong phys0; + ulong phys1; +}; + +struct +{ + Lock; + long machs; /* bitmap of processors */ + short exiting; + int ispanic; +} active; + +extern KMap kpte[]; +extern register Mach *m; +extern register Proc *up; + +extern FPsave initfp; +extern ulong memsize; + +extern int normalprint; + +/* + * 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]; +}; + +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 */ +}; + +typedef int regint; /* register sized int, ensure single instr'n */ --- /dev/null +++ /sys/src/9/loongson/devarch.c @@ -0,0 +1,240 @@ +#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 = 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; iqid.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, +}; + +static long +cputyperead(Chan*, void *a, long n, vlong offset) +{ + char str[128]; + + snprint(str, sizeof str, "MIPS LE %lud\n", m->hz / Mhz); + return readstr(offset, a, n, str); +} + +static long +tbread(Chan*, void *a, long n, vlong offset) +{ + char str[17]; + 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[17]; + uvlong tb; + + cycles(&tb); + + snprint(str, sizeof(str), "%16.16llux", (tb/700)* 1000); + return readstr(offset, a, n, str); +} + +char *cputype = "spim"; + +char *faultsprint(char *, char *); + +static long +archctlread(Chan*, void *a, long nn, vlong offset) +{ + int n; + char *buf, *p, *ep; + + p = buf = malloc(READSTR); + if(p == nil) + error(Enomem); + ep = p + READSTR; + p = seprint(p, ep, "cpu %s %lud\n", cputype, + (ulong)(m->hz+999999)/1000000); + p = seprint(p, ep, "stlb hash collisions"); + for (n = 0; n < conf.nmach; n++) + p = seprint(p, ep, " %d", MACHP(n)->hashcoll); + p = seprint(p, ep, "\n"); + p = seprint(p, ep, "NKTLB %d ktlb misses %ld utlb misses %ld\n", + NKTLB, m->ktlbfault, m->utlbfault); + faultsprint(p, ep); + n = readstr(offset, a, nn, buf); + free(buf); + return n; +} + +//static Cmdtab archctlmsg[]; + +static long +archctlwrite(Chan*, void *a, long n, vlong) +{ + Cmdbuf *cb; + Cmdtab *ct; + + cb = parsecmd(a, n); + if(waserror()){ + free(cb); + nexterror(); + } +// ct = lookupcmd(cb, archctlmsg, nelem(archctlmsg)); + cmderror(cb, "unknown control message"); + SET(ct); USED(ct); + + free(cb); + poperror(); + return n; +} + +void +archinit(void) +{ + addarchfile("cputype", 0444, cputyperead, nil); + addarchfile("timebase",0444, tbread, nil); + addarchfile("archctl", 0664, archctlread, archctlwrite); +// addarchfile("nsec", 0444, nsread, nil); +} --- /dev/null +++ /sys/src/9/loongson/devether.c @@ -0,0 +1,525 @@ +#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) != nil && (f->type == type || f->type < 0) && + (tome || multi || f->prom)){ + /* Don't want to hear bridged packets */ + if(f->bridge && !fromwire && !fromme) + continue; + if(!f->headersonly){ + if(fromwire && fx == 0) + fx = f; + else if(xbp = iallocb(len)){ + memmove(xbp->wp, pkt, len); + xbp->wp += len; + if(qpass(f->in, xbp) < 0) + ether->soverflows++; + } + else + ether->soverflows++; + } + else + etherrtrace(f, pkt, len); + } + } + + if(fx){ + if(qpass(fx->in, bp) < 0) + ether->soverflows++; + return 0; + } + if(fromwire){ + freeb(bp); + return 0; + } + return bp; +} + +static int +etheroq(Ether* ether, Block* bp) +{ + int len, loopback, s; + Etherpkt *pkt; + + ether->outpackets++; + + /* + * Check if the packet has to be placed back onto the input queue, + * i.e. if it's a loopback or broadcast packet or the interface is + * in promiscuous mode. + * If it's a loopback packet indicate to etheriq that the data isn't + * needed and return, etheriq will pass-on or free the block. + * To enable bridging to work, only packets that were originated + * by this interface are fed back. + */ + pkt = (Etherpkt*)bp->rp; + len = BLEN(bp); + loopback = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0; + if(loopback || memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) == 0 || ether->prom){ + s = splhi(); + etheriq(ether, bp, 0); + splx(s); + } + + if(!loopback){ + qbwrite(ether->oq, bp); + if(ether->transmit != nil) + ether->transmit(ether); + } else + freeb(bp); + + return len; +} + +static long +etherwrite(Chan* chan, void* buf, long n, vlong) +{ + Ether *ether; + Block *bp; + int nn, onoff; + Cmdbuf *cb; + + ether = etherxx[chan->dev]; + if(NETTYPE(chan->qid.path) != Ndataqid) { + nn = netifwrite(ether, chan, buf, n); + if(nn >= 0) + return nn; + cb = parsecmd(buf, n); + if(cb->f[0] && strcmp(cb->f[0], "nonblocking") == 0){ + if(cb->nf <= 1) + onoff = 1; + else + onoff = atoi(cb->f[1]); + qnoblock(ether->oq, onoff); + free(cb); + return n; + } + free(cb); + if(ether->ctl!=nil) + return ether->ctl(ether,buf,n); + + error(Ebadctl); + } + + if(n > ether->maxmtu) + error(Etoobig); + if(n < ether->minmtu) + error(Etoosmall); + + bp = allocb(n); + if(waserror()){ + freeb(bp); + nexterror(); + } + memmove(bp->rp, buf, n); + memmove(bp->rp+Eaddrlen, ether->ea, Eaddrlen); + poperror(); + bp->wp += n; + + return etheroq(ether, bp); +} + +static long +etherbwrite(Chan* chan, Block* bp, ulong) +{ + Ether *ether; + long n; + + n = BLEN(bp); + if(NETTYPE(chan->qid.path) != Ndataqid){ + if(waserror()) { + freeb(bp); + nexterror(); + } + n = etherwrite(chan, bp->rp, n, 0); + poperror(); + freeb(bp); + return n; + } + ether = etherxx[chan->dev]; + + if(n > ether->maxmtu){ + freeb(bp); + error(Etoobig); + } + if(n < ether->minmtu){ + freeb(bp); + error(Etoosmall); + } + + return etheroq(ether, bp); +} + +static struct { + char* type; + int (*reset)(Ether*); +} cards[MaxEther+1]; + +void +addethercard(char* t, int (*r)(Ether*)) +{ + static int ncard; + + if(ncard == MaxEther) + panic("too many ether cards"); + cards[ncard].type = t; + cards[ncard].reset = r; + ncard++; +} + +int +parseether(uchar *to, char *from) +{ + char nip[4]; + char *p; + int i; + + p = from; + for(i = 0; i < Eaddrlen; i++){ + if(*p == 0) + return -1; + nip[0] = *p++; + if(*p == 0) + return -1; + nip[1] = *p++; + nip[2] = 0; + to[i] = strtoul(nip, 0, 16); + if(*p == ':') + p++; + } + return 0; +} + +static void +etherreset(void) +{ + Ether *ether; + int i, n, ctlrno, subirq; + 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], + "100BASE-TXFD") == 0) + ether->mbps = 100; + if(cards[n].reset(ether)) + break; + snprint(name, sizeof(name), "ether%d", ctlrno); + + subirq = pcisubirq(ether->tbdf); + if(ether->interrupt != nil && ether->irq >= 0) + intrenable(ether->irq, ether->interrupt, ether, subirq); + + i = snprint(buf, sizeof buf, + "#l%d: %s: %dMbps port %#lux irq %d.%d", + ctlrno, ether->type, ether->mbps, ether->port, + ether->irq, subirq); + if(ether->mem) + i += snprint(buf+i, sizeof buf - i, + " addr %#p", 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"); + iprint("%s", buf); /* it may be too early for print */ + + if(ether->mbps >= 1000) + netifinit(ether, name, Ntypes, 4*1024*1024); + else if(ether->mbps >= 100) + netifinit(ether, name, Ntypes, 1024*1024); + else + netifinit(ether, name, Ntypes, 65*1024); + if(ether->oq == 0) + ether->oq = qopen(ether->limit, Qmsg, 0, 0); + if(ether->oq == 0) + panic("etherreset %s", name); + ether->alen = Eaddrlen; + memmove(ether->addr, ether->ea, Eaddrlen); + memset(ether->bcast, 0xFF, Eaddrlen); + + etherxx[ctlrno] = ether; + ether = 0; + break; + } + } + if(ether) + free(ether); +} + +static void +ethershutdown(void) +{ + Ether *ether; + int i; + + for(i = 0; i < MaxEther; i++){ + ether = etherxx[i]; + if(ether == nil) + continue; + if(ether->shutdown == nil) { + print("#l%d: no shutdown function\n", i); + continue; + } + (*ether->shutdown)(ether); + } +} + + +#define POLY 0xedb88320 + +/* really slow 32 bit crc for ethers */ +ulong +ethercrc(uchar *p, int len) +{ + int i, j; + ulong crc, b; + + crc = 0xffffffff; + for(i = 0; i < len; i++){ + b = *p++; + for(j = 0; j < 8; j++){ + crc = (crc>>1) ^ (((crc^b) & 1) ? POLY : 0); + b >>= 1; + } + } + return crc; +} + +void +dumpoq(Queue *oq) +{ + if (oq == nil) + print("no outq! "); + else if (qisclosed(oq)) + print("outq closed "); + else if (qfull(oq)) + print("outq full "); + else + print("outq %d ", qlen(oq)); +} + +void +dumpnetif(Netif *netif) +{ + print("netif %s ", netif->name); + print("limit %d mbps %d link %d ", + netif->limit, netif->mbps, netif->link); + print("inpkts %lld outpkts %lld errs %d\n", + netif->inpackets, netif->outpackets, + netif->crcs + netif->oerrs + netif->frames + netif->overflows + + netif->buffs + netif->soverflows); +} + +Dev etherdevtab = { + 'l', + "ether", + + etherreset, + devinit, + ethershutdown, + etherattach, + etherwalk, + etherstat, + etheropen, + ethercreate, + etherclose, + etherread, + etherbread, + etherwrite, + etherbwrite, + devremove, + etherwstat, +}; --- /dev/null +++ /sys/src/9/loongson/devrtc.c @@ -0,0 +1,344 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +/* + * real time clock + */ + +enum { + Paddr= 0x70, /* address port */ + Pdata= 0x71, /* data port */ + + Seconds= 0x00, + Minutes= 0x02, + Hours= 0x04, + Mday= 0x07, + Month= 0x08, + Year= 0x09, + Status= 0x0A, +}; + +typedef struct Rtc Rtc; +struct Rtc +{ + int sec; + int min; + int hour; + int mday; + int mon; + int year; +}; + +enum{ + Qdir = 0, + Qrtc, +}; + +Dirtab rtcdir[]={ + ".", {Qdir, 0, QTDIR}, 0, 0555, + "rtc", {Qrtc, 0}, 0, 0664, +}; + +static Lock rtclock; + +static ulong rtc2sec(Rtc*); +static void sec2rtc(ulong, Rtc*); + +static Chan* +rtcattach(char* spec) +{ + return devattach('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) +{ + omode = openmode(omode); + switch((ulong)c->qid.path){ + case Qrtc: + if(strcmp(up->user, eve)!=0 && omode!=OREAD) + error(Eperm); + break; + } + return devopen(c, omode, rtcdir, nelem(rtcdir), devgen); +} + +static void +rtcclose(Chan*) +{ +} + +static long +_rtctime(void) +{ + Rtc rtc; + int i; + + /* don't do the read until the clock is no longer busy */ + for(i = 0; i < 10000; i++){ + outb(Paddr, Status); + if(inb(Pdata) & 0x80) + continue; + + /* read clock values */ + outb(Paddr, Seconds); rtc.sec = inb(Pdata); + outb(Paddr, Minutes); rtc.min = inb(Pdata); + outb(Paddr, Hours); rtc.hour = inb(Pdata); + outb(Paddr, Mday); rtc.mday = inb(Pdata); + outb(Paddr, Month); rtc.mon = inb(Pdata); + outb(Paddr, Year); rtc.year = inb(Pdata); + + outb(Paddr, Status); + if((inb(Pdata) & 0x80) == 0) + break; + } + + /* + * the world starts jan 1 1970 + */ + if(rtc.year < 70) + rtc.year += 2000; + else + rtc.year += 1900; + return rtc2sec(&rtc); +} + +long +rtctime(void) +{ + int i; + long t, ot; + + ilock(&rtclock); + + /* loop till we get two reads in a row the same */ + t = _rtctime(); + for(i = 0; i < 100; i++){ + ot = t; + t = _rtctime(); + if(ot == t) + break; + } + if(i == 100) print("we are boofheads\n"); + + iunlock(&rtclock); + + return t; +} + +static long +rtcread(Chan* c, void* buf, long n, vlong off) +{ + ulong t; + ulong offset = off; + + if(c->qid.type & QTDIR) + return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen); + + switch((ulong)c->qid.path){ + case Qrtc: + t = rtctime(); + n = readnum(offset, buf, n, t, 12); + return n; + } + error(Ebadarg); + return 0; +} + +static long +rtcwrite(Chan* c, void* buf, long n, vlong off) +{ + Rtc rtc; + ulong secs; + char *cp, *ep; + ulong offset = off; + + if(offset!=0) + error(Ebadarg); + + switch((ulong)c->qid.path){ + case Qrtc: + /* + * read the time + */ + cp = ep = buf; + ep += n; + while(cp < ep){ + if(*cp>='0' && *cp<='9') + break; + cp++; + } + secs = strtoul(cp, 0, 0); + + /* + * convert to rtc + */ + sec2rtc(secs, &rtc); + + /* + * write the clock + */ + ilock(&rtclock); + outb(Paddr, Seconds); outb(Pdata, rtc.sec); + outb(Paddr, Minutes); outb(Pdata, rtc.min); + outb(Paddr, Hours); outb(Pdata, rtc.hour); + outb(Paddr, Mday); outb(Pdata, rtc.mday); + outb(Paddr, Month); outb(Pdata, rtc.mon); + outb(Paddr, Year); outb(Pdata, rtc.year % 100); + iunlock(&rtclock); + return n; + } + error(Ebadarg); + return 0; +} + +Dev rtcdevtab = { + 'r', + "rtc", + + devreset, + devinit, + devshutdown, + rtcattach, + rtcwalk, + rtcstat, + rtcopen, + devcreate, + rtcclose, + rtcread, + devbread, + rtcwrite, + devbwrite, + devremove, + devwstat, +}; + +#define SEC2MIN 60L +#define SEC2HOUR (60L*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 y) +{ + if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0)) + return ldmsize; + else + return dmsize; +} + +/* + * compute seconds since Jan 1 1970 + */ +static ulong +rtc2sec(Rtc *rtc) +{ + ulong secs; + int i; + int *d2m; + + secs = 0; + + /* + * seconds per year + */ + 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; + } + + /* + * 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; +} --- /dev/null +++ /sys/src/9/loongson/devusb.c @@ -0,0 +1,1458 @@ +/* + * 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 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= 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); + panic("usb: epalloc: too few endpoints (%d)", Neps); + } + 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(hp->irq, hp->interrupt, hp, pcisubirq(hp->tbdf)); + + print("#u/usb/ep%d.0: %s: port 0x%lux irq %d\n", + epnb, hcitypes[cardno].type, hp->port, hp->irq); + epnb++; + return hp; +} + +static void +usbreset(void) +{ + int cardno, ctlrno; + Hci *hp; + + if(getconf("*nousbprobe")) + return; + 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 (%d) too small\n", Nhcis); +} + +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, +}; --- /dev/null +++ /sys/src/9/loongson/ether8139.c @@ -0,0 +1,887 @@ +/* + * Realtek 8139 (but not the 8129). + * Error recovery for the various over/under -flow conditions + * may need work. + */ +#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" + +enum { /* registers */ + Idr0 = 0x0000, /* MAC address */ + Mar0 = 0x0008, /* Multicast address */ + Tsd0 = 0x0010, /* Transmit Status Descriptor0 */ + Tsad0 = 0x0020, /* Transmit Start Address Descriptor0 */ + Rbstart = 0x0030, /* Receive Buffer Start Address */ + Erbcr = 0x0034, /* Early Receive Byte Count */ + Ersr = 0x0036, /* Early Receive Status */ + Cr = 0x0037, /* Command Register */ + Capr = 0x0038, /* Current Address of Packet Read */ + Cbr = 0x003A, /* Current Buffer Address */ + Imr = 0x003C, /* Interrupt Mask */ + Isr = 0x003E, /* Interrupt Status */ + Tcr = 0x0040, /* Transmit Configuration */ + Rcr = 0x0044, /* Receive Configuration */ + Tctr = 0x0048, /* Timer Count */ + Mpc = 0x004C, /* Missed Packet Counter */ + Cr9346 = 0x0050, /* 9346 Command Register */ + Config0 = 0x0051, /* Configuration Register 0 */ + Config1 = 0x0052, /* Configuration Register 1 */ + TimerInt = 0x0054, /* Timer Interrupt */ + Msr = 0x0058, /* Media Status */ + Config3 = 0x0059, /* Configuration Register 3 */ + Config4 = 0x005A, /* Configuration Register 4 */ + Mulint = 0x005C, /* Multiple Interrupt Select */ + RerID = 0x005E, /* PCI Revision ID */ + Tsad = 0x0060, /* Transmit Status of all Descriptors */ + + Bmcr = 0x0062, /* Basic Mode Control */ + Bmsr = 0x0064, /* Basic Mode Status */ + Anar = 0x0066, /* Auto-Negotiation Advertisment */ + Anlpar = 0x0068, /* Auto-Negotiation Link Partner */ + Aner = 0x006A, /* Auto-Negotiation Expansion */ + Dis = 0x006C, /* Disconnect Counter */ + Fcsc = 0x006E, /* False Carrier Sense Counter */ + Nwaytr = 0x0070, /* N-way Test */ + Rec = 0x0072, /* RX_ER Counter */ + Cscr = 0x0074, /* CS Configuration */ + Phy1parm = 0x0078, /* PHY Parameter 1 */ + Twparm = 0x007C, /* Twister Parameter */ + Phy2parm = 0x0080, /* PHY Parameter 2 */ +}; + +enum { /* Cr */ + Bufe = 0x01, /* Rx Buffer Empty */ + Te = 0x04, /* Transmitter Enable */ + Re = 0x08, /* Receiver Enable */ + Rst = 0x10, /* Software Reset */ +}; + +enum { /* Imr/Isr */ + Rok = 0x0001, /* Receive OK */ + Rer = 0x0002, /* Receive Error */ + Tok = 0x0004, /* Transmit OK */ + Ter = 0x0008, /* Transmit Error */ + Rxovw = 0x0010, /* Receive Buffer Overflow */ + PunLc = 0x0020, /* Packet Underrun or Link Change */ + Fovw = 0x0040, /* Receive FIFO Overflow */ + Clc = 0x2000, /* Cable Length Change */ + Timerbit = 0x4000, /* Timer */ + Serr = 0x8000, /* System Error */ +}; + +enum { /* Tcr */ + Clrabt = 0x00000001, /* Clear Abort */ + TxrrSHIFT = 4, /* Transmit Retry Count */ + TxrrMASK = 0x000000F0, + MtxdmaSHIFT = 8, /* Max. DMA Burst Size */ + MtxdmaMASK = 0x00000700, + Mtxdma2048 = 0x00000700, + Acrc = 0x00010000, /* Append CRC (not) */ + LbkSHIFT = 17, /* Loopback Test */ + LbkMASK = 0x00060000, + Rtl8139ArevG = 0x00800000, /* RTL8139A Rev. G ID */ + IfgSHIFT = 24, /* Interframe Gap */ + IfgMASK = 0x03000000, + HwveridSHIFT = 26, /* Hardware Version ID */ + HwveridMASK = 0x7C000000, +}; + +enum { /* Rcr */ + Aap = 0x00000001, /* Accept All Packets */ + Apm = 0x00000002, /* Accept Physical Match */ + Am = 0x00000004, /* Accept Multicast */ + Ab = 0x00000008, /* Accept Broadcast */ + Ar = 0x00000010, /* Accept Runt */ + Aer = 0x00000020, /* Accept Error */ + Sel9356 = 0x00000040, /* 9356 EEPROM used */ + Wrap = 0x00000080, /* Rx Buffer Wrap Control */ + MrxdmaSHIFT = 8, /* Max. DMA Burst Size */ + MrxdmaMASK = 0x00000700, + Mrxdmaunlimited = 0x00000700, + RblenSHIFT = 11, /* Receive Buffer Length */ + RblenMASK = 0x00001800, + Rblen8K = 0x00000000, /* 8KB+16 */ + Rblen16K = 0x00000800, /* 16KB+16 */ + Rblen32K = 0x00001000, /* 32KB+16 */ + Rblen64K = 0x00001800, /* 64KB+16 */ + RxfthSHIFT = 13, /* Receive Buffer Length */ + RxfthMASK = 0x0000E000, + Rxfth256 = 0x00008000, + Rxfthnone = 0x0000E000, + Rer8 = 0x00010000, /* Accept Error Packets > 8 bytes */ + MulERINT = 0x00020000, /* Multiple Early Interrupt Select */ + ErxthSHIFT = 24, /* Early Rx Threshold */ + ErxthMASK = 0x0F000000, + Erxthnone = 0x00000000, +}; + +enum { /* Received Packet Status */ + Rcok = 0x0001, /* Receive Completed OK */ + Fae = 0x0002, /* Frame Alignment Error */ + Crc = 0x0004, /* CRC Error */ + Long = 0x0008, /* Long Packet */ + Runt = 0x0010, /* Runt Packet Received */ + Ise = 0x0020, /* Invalid Symbol Error */ + Bar = 0x2000, /* Broadcast Address Received */ + Pam = 0x4000, /* Physical Address Matched */ + Mar = 0x8000, /* Multicast Address Received */ +}; + +enum { /* Media Status Register */ + Rxpf = 0x01, /* Pause Flag */ + Txpf = 0x02, /* Pause Flag */ + Linkb = 0x04, /* Inverse of Link Status */ + Speed10 = 0x08, /* 10Mbps */ + Auxstatus = 0x10, /* Aux. Power Present Status */ + Rxfce = 0x40, /* Receive Flow Control Enable */ + Txfce = 0x80, /* Transmit Flow Control Enable */ +}; + +typedef struct Td Td; +struct Td { /* Soft Transmit Descriptor */ + int tsd; + int tsad; + uchar* data; + Block* bp; +}; + +enum { /* Tsd0 */ + SizeSHIFT = 0, /* Descriptor Size */ + SizeMASK = 0x00001FFF, + Own = 0x00002000, + Tun = 0x00004000, /* Transmit FIFO Underrun */ + Tcok = 0x00008000, /* Transmit COmpleted OK */ + EtxthSHIFT = 16, /* Early Tx Threshold */ + EtxthMASK = 0x001F0000, + NccSHIFT = 24, /* Number of Collisions Count */ + NccMASK = 0x0F000000, + Cdh = 0x10000000, /* CD Heartbeat */ + Owc = 0x20000000, /* Out of Window Collision */ + Tabt = 0x40000000, /* Transmit Abort */ + Crs = 0x80000000, /* Carrier Sense Lost */ +}; + +enum { + Rblen = Rblen64K, /* Receive Buffer Length */ + Ntd = 4, /* Number of Transmit Descriptors */ + Tdbsz = ROUNDUP(sizeof(Etherpkt), 4), +}; + +typedef struct Ctlr Ctlr; +typedef struct Ctlr { + int port; + Pcidev* pcidev; + Ctlr* next; + int active; + int id; + + QLock alock; /* attach */ + Lock ilock; /* init */ + void* alloc; /* base of per-Ctlr allocated data */ + + int pcie; /* flag: pci-express device? */ + + uvlong mchash; /* multicast hash */ + + int rcr; /* receive configuration register */ + uchar* rbstart; /* receive buffer */ + int rblen; /* receive buffer length */ + int ierrs; /* receive errors */ + + Lock tlock; /* transmit */ + Td td[Ntd]; + int ntd; /* descriptors active */ + int tdh; /* host index into td */ + int tdi; /* interface index into td */ + int etxth; /* early transmit threshold */ + int taligned; /* packet required no alignment */ + int tunaligned; /* packet required alignment */ + + int dis; /* disconnect counter */ + int fcsc; /* false carrier sense counter */ + int rec; /* RX_ER counter */ + uint mcast; +} Ctlr; + +static Ctlr* ctlrhead; +static Ctlr* ctlrtail; + +#define csr8r(c, r) (inb((c)->port+(r))) +#define csr16r(c, r) (ins((c)->port+(r))) +#define csr32r(c, r) (inl((c)->port+(r))) +#define csr8w(c, r, b) (outb((c)->port+(r), (int)(b))) +#define csr16w(c, r, w) (outs((c)->port+(r), (ushort)(w))) +#define csr32w(c, r, l) (outl((c)->port+(r), (ulong)(l))) + +static void +rtl8139promiscuous(void* arg, int on) +{ + Ether *edev; + Ctlr * ctlr; + + edev = arg; + ctlr = edev->ctlr; + ilock(&ctlr->ilock); + + if(on) + ctlr->rcr |= Aap; + else + ctlr->rcr &= ~Aap; + csr32w(ctlr, Rcr, ctlr->rcr); + iunlock(&ctlr->ilock); +} + +enum { + /* everyone else uses 0x04c11db7, but they both produce the same crc */ + Etherpolybe = 0x04c11db6, + Bytemask = (1<<8) - 1, +}; + +static ulong +ethercrcbe(uchar *addr, long len) +{ + int i, j; + ulong c, crc, carry; + + crc = ~0UL; + for (i = 0; i < len; i++) { + c = addr[i]; + for (j = 0; j < 8; j++) { + carry = ((crc & (1UL << 31))? 1: 0) ^ (c & 1); + crc <<= 1; + c >>= 1; + if (carry) + crc = (crc ^ Etherpolybe) | carry; + } + } + return crc; +} + +static ulong +swabl(ulong l) +{ + return l>>24 | (l>>8) & (Bytemask<<8) | + (l<<8) & (Bytemask<<16) | l<<24; +} + +static void +rtl8139multicast(void* ether, uchar *eaddr, int add) +{ + Ether *edev; + Ctlr *ctlr; + + if (!add) + return; /* ok to keep receiving on old mcast addrs */ + + edev = ether; + ctlr = edev->ctlr; + ilock(&ctlr->ilock); + + ctlr->mchash |= 1ULL << (ethercrcbe(eaddr, Eaddrlen) >> 26); + + ctlr->rcr |= Am; + csr32w(ctlr, Rcr, ctlr->rcr); + + /* pci-e variants reverse the order of the hash byte registers */ + if (0 && ctlr->pcie) { + csr32w(ctlr, Mar0, swabl(ctlr->mchash>>32)); + csr32w(ctlr, Mar0+4, swabl(ctlr->mchash)); + } else { + csr32w(ctlr, Mar0, ctlr->mchash); + csr32w(ctlr, Mar0+4, ctlr->mchash>>32); + } + + iunlock(&ctlr->ilock); +} + +static long +rtl8139ifstat(Ether* edev, void* a, long n, ulong offset) +{ + int l; + char *p; + Ctlr *ctlr; + + ctlr = edev->ctlr; + p = malloc(READSTR); + if(p == nil) + error(Enomem); + l = snprint(p, READSTR, "rcr %#8.8ux\n", ctlr->rcr); + l += snprint(p+l, READSTR-l, "multicast %ud\n", ctlr->mcast); + l += snprint(p+l, READSTR-l, "ierrs %d\n", ctlr->ierrs); + l += snprint(p+l, READSTR-l, "etxth %d\n", ctlr->etxth); + l += snprint(p+l, READSTR-l, "taligned %d\n", ctlr->taligned); + l += snprint(p+l, READSTR-l, "tunaligned %d\n", ctlr->tunaligned); + ctlr->dis += csr16r(ctlr, Dis); + l += snprint(p+l, READSTR-l, "dis %d\n", ctlr->dis); + ctlr->fcsc += csr16r(ctlr, Fcsc); + l += snprint(p+l, READSTR-l, "fcscnt %d\n", ctlr->fcsc); + ctlr->rec += csr16r(ctlr, Rec); + l += snprint(p+l, READSTR-l, "rec %d\n", ctlr->rec); + + l += snprint(p+l, READSTR-l, "Tsd0-3 %#8.8lux %#8.8lux %#8.8lux %#8.8lux\n", + csr32r(ctlr, Tsd0), csr32r(ctlr, Tsd0+4), + csr32r(ctlr, Tsd0+8), csr32r(ctlr, Tsd0+12)); + l += snprint(p+l, READSTR-l, "Cr %#2.2ux\n", csr8r(ctlr, Cr)); + l += snprint(p+l, READSTR-l, "Tcr %#8.8lux\n", csr32r(ctlr, Tcr)); + l += snprint(p+l, READSTR-1, "Rcr %#8.8lux\n", csr32r(ctlr, Rcr)); + l += snprint(p+l, READSTR-1, "Rbstart %#8.8lux\n", csr32r(ctlr, Rbstart)); + l += snprint(p+l, READSTR-1, "Capr %#4.4ux\n", csr16r(ctlr, Capr)); + l += snprint(p+l, READSTR-1, "Cbr %#4.4ux\n", csr16r(ctlr, Cbr)); + l += snprint(p+l, READSTR-l, "Config0 %#2.2ux\n", csr8r(ctlr, Config0)); + l += snprint(p+l, READSTR-l, "Config1 %#2.2ux\n", csr8r(ctlr, Config1)); + l += snprint(p+l, READSTR-l, "Msr %#2.2ux\n", csr8r(ctlr, Msr)); + l += snprint(p+l, READSTR-l, "Config3 %#2.2ux\n", csr8r(ctlr, Config3)); + l += snprint(p+l, READSTR-l, "Config4 %#2.2ux\n", csr8r(ctlr, Config4)); + + l += snprint(p+l, READSTR-l, "Bmcr %#4.4ux\n", csr16r(ctlr, Bmcr)); + l += snprint(p+l, READSTR-l, "Bmsr %#4.4ux\n", csr16r(ctlr, Bmsr)); + l += snprint(p+l, READSTR-l, "Anar %#4.4ux\n", csr16r(ctlr, Anar)); + l += snprint(p+l, READSTR-l, "Anlpar %#4.4ux\n", csr16r(ctlr, Anlpar)); + l += snprint(p+l, READSTR-l, "Aner %#4.4ux\n", csr16r(ctlr, Aner)); + l += snprint(p+l, READSTR-l, "Nwaytr %#4.4ux\n", csr16r(ctlr, Nwaytr)); + snprint(p+l, READSTR-l, "Cscr %#4.4ux\n", csr16r(ctlr, Cscr)); + n = readstr(offset, a, n, p); + free(p); + + return n; +} + +static int +rtl8139reset(Ctlr* ctlr) +{ + int timeo; + + /* stop interrupts */ + csr16w(ctlr, Imr, 0); + csr16w(ctlr, Isr, ~0); + csr32w(ctlr, TimerInt, 0); + + /* + * Soft reset the controller. + */ + csr8w(ctlr, Cr, Rst); + for(timeo = 0; timeo < 1000; timeo++){ + if(!(csr8r(ctlr, Cr) & Rst)) + return 0; + delay(1); + } + + return -1; +} + +static void +rtl8139halt(Ctlr* ctlr) +{ + int i; + + csr8w(ctlr, Cr, 0); + csr16w(ctlr, Imr, 0); + csr16w(ctlr, Isr, ~0); + csr32w(ctlr, TimerInt, 0); + + for(i = 0; i < Ntd; i++){ + if(ctlr->td[i].bp == nil) + continue; + freeb(ctlr->td[i].bp); + ctlr->td[i].bp = nil; + } +} + +static void +rtl8139shutdown(Ether *edev) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + ilock(&ctlr->ilock); + rtl8139halt(ctlr); + rtl8139reset(ctlr); + iunlock(&ctlr->ilock); +} + +static void +rtl8139init(Ether* edev) +{ + int i; + ulong r; + Ctlr *ctlr; + uchar *alloc; + + ctlr = edev->ctlr; + ilock(&ctlr->ilock); + + rtl8139halt(ctlr); + + /* + * MAC Address. + */ + r = (edev->ea[3]<<24)|(edev->ea[2]<<16)|(edev->ea[1]<<8)|edev->ea[0]; + csr32w(ctlr, Idr0, r); + r = (edev->ea[5]<<8)|edev->ea[4]; + csr32w(ctlr, Idr0+4, r); + + /* + * Receiver + */ + alloc = (uchar*)ROUNDUP((uintptr)ctlr->alloc, 32); + ctlr->rbstart = KSEG1ADDR(alloc); + alloc += ctlr->rblen+16; + memset(ctlr->rbstart, 0, ctlr->rblen+16); + csr32w(ctlr, Rbstart, PCIWADDR(ctlr->rbstart)); + ctlr->rcr = Rxfth256|Rblen|Mrxdmaunlimited|Ab|Am|Apm; + + /* + * Transmitter. + */ + for(i = 0; i < Ntd; i++){ + ctlr->td[i].tsd = Tsd0+i*4; + ctlr->td[i].tsad = Tsad0+i*4; + ctlr->td[i].data = alloc; + alloc += Tdbsz; + ctlr->td[i].bp = nil; + } + ctlr->ntd = ctlr->tdh = ctlr->tdi = 0; + ctlr->etxth = 128/32; + + /* + * Enable receiver/transmitter. + * Need to enable before writing the Rcr or it won't take. + */ + csr8w(ctlr, Cr, Te|Re); + csr32w(ctlr, Tcr, Mtxdma2048); + csr32w(ctlr, Rcr, ctlr->rcr); + csr32w(ctlr, Mar0, 0); + csr32w(ctlr, Mar0+4, 0); + ctlr->mchash = 0; + + /* + * Interrupts. + */ + csr32w(ctlr, TimerInt, 0); + csr16w(ctlr, Imr, Serr|Timerbit|Fovw|PunLc|Rxovw|Ter|Tok|Rer|Rok); + csr32w(ctlr, Mpc, 0); + + iunlock(&ctlr->ilock); +} + +static void +rtl8139attach(Ether* edev) +{ + Ctlr *ctlr; + + if(edev == nil) { + print("rtl8139attach: nil edev\n"); + return; + } + ctlr = edev->ctlr; + if(ctlr == nil) { + print("rtl8139attach: nil ctlr for Ether %#p\n", edev); + return; + } + qlock(&ctlr->alock); + if(ctlr->alloc == nil){ + ctlr->rblen = 1<<((Rblen>>RblenSHIFT)+13); + ctlr->alloc = malloc(ctlr->rblen+16 + Ntd*Tdbsz + 32); + if(ctlr->alloc == nil) { + qunlock(&ctlr->alock); + error(Enomem); + } + rtl8139init(edev); + } + qunlock(&ctlr->alock); +} + +static void +rtl8139txstart(Ether* edev) +{ + Td *td; + int size; + Block *bp; + Ctlr *ctlr; + + ctlr = edev->ctlr; + while(ctlr->ntd < Ntd){ + bp = qget(edev->oq); + if(bp == nil) + break; + size = BLEN(bp); + + td = &ctlr->td[ctlr->tdh]; + memmove(KSEG1ADDR(td->data), bp->rp, size); + freeb(bp); + csr32w(ctlr, td->tsad, PCIWADDR(td->data)); + ctlr->tunaligned++; +/* if(1 || ((int)bp->rp) & 0x03){ + memmove(KSEG1ADDR(td->data), bp->rp, size); + freeb(bp); + csr32w(ctlr, td->tsad, PCIWADDR(td->data)); + ctlr->tunaligned++; + } + else{ + td->bp = bp; + csr32w(ctlr, td->tsad, PCIWADDR(bp->rp)); + ctlr->taligned++; + } +*/ + csr32w(ctlr, td->tsd, (ctlr->etxth<ntd++; + ctlr->tdh = NEXT(ctlr->tdh, Ntd); + } +} + +static void +rtl8139transmit(Ether* edev) +{ + Ctlr *ctlr; + + ctlr = edev->ctlr; + ilock(&ctlr->tlock); + rtl8139txstart(edev); + iunlock(&ctlr->tlock); +} + +static void +rtl8139receive(Ether* edev) +{ + Block *bp; + Ctlr *ctlr; + ushort capr; + uchar cr, *p; + int l, length, status; + + ctlr = edev->ctlr; + + /* + * Capr is where the host is reading from, + * Cbr is where the NIC is currently writing. + */ + if(ctlr->rblen == 0) + return; /* not attached yet (shouldn't happen) */ + capr = (csr16r(ctlr, Capr)+16) % ctlr->rblen; + while(!(csr8r(ctlr, Cr) & Bufe)){ + p = ctlr->rbstart+capr; + + /* + * Apparently the packet length may be 0xFFF0 if + * the NIC is still copying the packet into memory. + */ + length = (*(p+3)<<8)|*(p+2); + if(length == 0xFFF0) + break; + status = (*(p+1)<<8)|*p; + + if(!(status & Rcok)){ + if(status & (Ise|Fae)) + edev->frames++; + if(status & Crc) + edev->crcs++; + if(status & (Runt|Long)) + edev->buffs++; + + /* + * Reset the receiver. + * Also may have to restore the multicast list + * here too if it ever gets used. + */ + cr = csr8r(ctlr, Cr); + csr8w(ctlr, Cr, cr & ~Re); + csr32w(ctlr, Rbstart, PCIWADDR(ctlr->rbstart)); + csr8w(ctlr, Cr, cr); + csr32w(ctlr, Rcr, ctlr->rcr); + + continue; + } + + /* + * Receive Completed OK. + * Very simplistic; there are ways this could be done + * without copying, but the juice probably isn't worth + * the squeeze. + * The packet length includes a 4 byte CRC on the end. + */ + capr = (capr+4) % ctlr->rblen; + p = ctlr->rbstart+capr; + capr = (capr+length) % ctlr->rblen; + if(status & Mar) + ctlr->mcast++; + + if((bp = iallocb(length)) != nil){ + if(p+length >= ctlr->rbstart+ctlr->rblen){ + l = ctlr->rbstart+ctlr->rblen - p; + memmove(bp->wp, p, l); + bp->wp += l; + length -= l; + p = ctlr->rbstart; + } + if(length > 0){ + memmove(bp->wp, p, length); + bp->wp += length; + } + bp->wp -= 4; + etheriq(edev, bp, 1); + } + + capr = ROUNDUP(capr, 4); + csr16w(ctlr, Capr, capr-16); + } +} + +static void +rtl8139interrupt(Ureg*, void* arg) +{ + Td *td; + Ctlr *ctlr; + Ether *edev; + int isr, msr, tsd; + + edev = arg; + ctlr = edev->ctlr; + if(ctlr == nil) { /* not attached yet? (shouldn't happen) */ + print("rtl8139interrupt: interrupt for unattached Ether %#p\n", + edev); + return; + } + + while((isr = csr16r(ctlr, Isr)) != 0){ + csr16w(ctlr, Isr, isr); + if(ctlr->alloc == nil) { + print("rtl8139interrupt: interrupt for unattached Ctlr " + "%#p port %#p\n", ctlr, (void *)ctlr->port); + return; /* not attached yet (shouldn't happen) */ + } + if(isr & (Fovw|PunLc|Rxovw|Rer|Rok)){ + rtl8139receive(edev); + if(!(isr & Rok)) + ctlr->ierrs++; + isr &= ~(Fovw|Rxovw|Rer|Rok); + } + + if(isr & (Ter|Tok)){ + ilock(&ctlr->tlock); + while(ctlr->ntd){ + td = &ctlr->td[ctlr->tdi]; + tsd = csr32r(ctlr, td->tsd); + if(!(tsd & (Tabt|Tun|Tcok))) + break; + + if(!(tsd & Tcok)){ + if(tsd & Tun){ + if(ctlr->etxth < ETHERMAXTU/32) + ctlr->etxth++; + } + edev->oerrs++; + } + + if(td->bp != nil){ + freeb(td->bp); + td->bp = nil; + } + + ctlr->ntd--; + ctlr->tdi = NEXT(ctlr->tdi, Ntd); + } + rtl8139txstart(edev); + iunlock(&ctlr->tlock); + isr &= ~(Ter|Tok); + } + + if(isr & PunLc){ + /* + * Maybe the link changed - do we care very much? + */ + msr = csr8r(ctlr, Msr); + if(!(msr & Linkb)){ + if(!(msr & Speed10) && edev->mbps != 100){ + edev->mbps = 100; + qsetlimit(edev->oq, 256*1024); + } + else if((msr & Speed10) && edev->mbps != 10){ + edev->mbps = 10; + qsetlimit(edev->oq, 65*1024); + } + } + isr &= ~(Clc|PunLc); + } + + /* + * Only Serr|Timerbit should be left by now. + * Should anything be done to tidy up? TimerInt isn't + * used so that can be cleared. A PCI bus error is indicated + * by Serr, that's pretty serious; is there anyhing to do + * other than try to reinitialise the chip? + */ + if((isr & (Serr|Timerbit)) != 0){ + iprint("rtl8139interrupt: imr %#4.4ux isr %#4.4ux\n", + csr16r(ctlr, Imr), isr); + if(isr & Timerbit) + csr32w(ctlr, TimerInt, 0); + if(isr & Serr) + rtl8139init(edev); + } + } +} + +static Ctlr* +rtl8139match(Ether* edev, int id) +{ + Pcidev *p; + Ctlr *ctlr; + int i, port; + + /* + * Any adapter matches if no edev->port is supplied, + * otherwise the ports must match. + */ + for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){ + if(ctlr->active) + continue; + p = ctlr->pcidev; + if(((p->did<<16)|p->vid) != id) + continue; + port = p->mem[0].bar & ~0x01; + if(edev->port != 0 && edev->port != port) + continue; + + pcisetioe(p); + + if(pcigetpms(p) > 0){ + pcisetpms(p, 0); + + for(i = 0; i < 6; i++) + pcicfgw32(p, PciBAR0+i*4, p->mem[i].bar); + pcicfgw8(p, PciINTL, p->intl); + pcicfgw8(p, PciLTR, p->ltr); + pcicfgw8(p, PciCLS, p->cls); + pcicfgw16(p, PciPCR, p->pcr); + } + + ctlr->port = port; + if(rtl8139reset(ctlr)) + continue; + pcisetbme(p); + + ctlr->active = 1; + return ctlr; + } + return nil; +} + +static struct { + char* name; + int id; +} rtl8139pci[] = { + { "rtl8139", (0x8139<<16)|0x10EC, }, /* generic */ + { "smc1211", (0x1211<<16)|0x1113, }, /* SMC EZ-Card */ + { "dfe-538tx", (0x1300<<16)|0x1186, }, /* D-Link DFE-538TX */ + { "dfe-560txd", (0x1340<<16)|0x1186, }, /* D-Link DFE-560TXD */ + { nil }, +}; + +static int +rtl8139pnp(Ether* edev) +{ + int i, id; + Pcidev *p; + Ctlr *ctlr; + uchar ea[Eaddrlen]; + + /* + * Make a list of all ethernet controllers + * if not already done. + */ + if(ctlrhead == nil){ + p = nil; + while(p = pcimatch(p, 0, 0)){ + if(p->ccrb != 0x02 || p->ccru != 0) + continue; + ctlr = malloc(sizeof(Ctlr)); + if(ctlr == nil) + error(Enomem); + ctlr->pcidev = p; + ctlr->id = (p->did<<16)|p->vid; + + if(ctlrhead != nil) + ctlrtail->next = ctlr; + else + ctlrhead = ctlr; + ctlrtail = ctlr; + } + } + + /* + * Is it an RTL8139 under a different name? + * Normally a search is made through all the found controllers + * for one which matches any of the known vid+did pairs. + * If a vid+did pair is specified a search is made for that + * specific controller only. + */ + id = 0; + for(i = 0; i < edev->nopt; i++){ + if(cistrncmp(edev->opt[i], "id=", 3) == 0) + id = strtol(&edev->opt[i][3], nil, 0); + } + + ctlr = nil; + if(id != 0) + ctlr = rtl8139match(edev, id); + else for(i = 0; rtl8139pci[i].name; i++){ + if((ctlr = rtl8139match(edev, rtl8139pci[i].id)) != nil) + break; + } + if(ctlr == nil) + return -1; + + edev->ctlr = ctlr; + edev->port = ctlr->port; +// edev->irq = ctlr->pcidev->intl; + edev->tbdf = ctlr->pcidev->tbdf; + + /* + * Check if the adapter's station address is to be overridden. + * If not, read it from the device and set in edev->ea. + */ + memset(ea, 0, Eaddrlen); + if(memcmp(ea, edev->ea, Eaddrlen) == 0){ + i = csr32r(ctlr, Idr0); + edev->ea[0] = i; + edev->ea[1] = i>>8; + edev->ea[2] = i>>16; + edev->ea[3] = i>>24; + i = csr32r(ctlr, Idr0+4); + edev->ea[4] = i; + edev->ea[5] = i>>8; + } + + edev->arg = edev; + edev->attach = rtl8139attach; + edev->transmit = rtl8139transmit; + edev->interrupt = rtl8139interrupt; + edev->ifstat = rtl8139ifstat; + + edev->promiscuous = rtl8139promiscuous; + edev->multicast = rtl8139multicast; + edev->shutdown = rtl8139shutdown; + + /* + * This should be much more dynamic but will do for now. + */ + if((csr8r(ctlr, Msr) & (Speed10|Linkb)) == 0) + edev->mbps = 100; + + return 0; +} + +void +ether8139link(void) +{ + addethercard("rtl8139", rtl8139pnp); +} --- /dev/null +++ /sys/src/9/loongson/etherif.h @@ -0,0 +1,42 @@ +enum +{ + MaxEther = 4, + Ntypes = 8, +}; + +typedef struct Ether Ether; +struct Ether { + RWlock; + ISAConf; /* hardware info */ + + int ctlrno; + int minmtu; + int maxmtu; + + Netif; + + void (*attach)(Ether*); /* filled in by reset routine */ + void (*detach)(Ether*); + void (*transmit)(Ether*); + void (*interrupt)(Ureg*, void*); + long (*ifstat)(Ether*, void*, long, ulong); + long (*ctl)(Ether*, void*, long); /* custom ctl messages */ + void (*power)(Ether*, int); /* power on/off */ + void (*shutdown)(Ether*); /* shutdown hardware before reboot */ + + void* ctlr; + uchar ea[Eaddrlen]; + void* address; + int irq; + int tbdf; + + Queue* oq; +}; + +extern Block* etheriq(Ether*, Block*, int); +extern void addethercard(char*, int(*)(Ether*)); +extern ulong ethercrc(uchar*, int); +extern int parseether(uchar*, char*); + +#define NEXT(x, l) (((x)+1)%(l)) +#define PREV(x, l) (((x) == 0) ? (l)-1: (x)-1) --- /dev/null +++ /sys/src/9/loongson/faultmips.c @@ -0,0 +1,289 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "ureg.h" +#include "../port/error.h" +#include "io.h" + +enum { + Debug = 0, +}; + +typedef struct Fault Fault; +struct Fault { + uintptr va; + ulong pid; + uintptr pc; + int cnt; + char *prog; + int code; +}; + +extern char *excname[]; + +static Fault lflt, maxflt; + +#define OFR(r) (uintptr)offsetof(Ureg, r) /* offset into Ureg of r */ +#define REG(ur, r) *acpureg(ur, r) /* cpu reg in Ureg */ +#define REGMASK MASK(5) + +static ulong dummyr0; + +static int roff[32] = { + 0, 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(r15), + OFR(r16), OFR(r17), OFR(r18), OFR(r19), + OFR(r20), OFR(r21), OFR(r22), OFR(r23), + OFR(r24), OFR(r25), OFR(r26), OFR(r27), + OFR(r28), OFR(sp), OFR(r30), OFR(r31), +}; + +static ulong * +acpureg(Ureg *ur, int r) +{ + r &= REGMASK; + if (r == 0 || roff[r] == 0) { + dummyr0 = 0; + return &dummyr0; + } + return (ulong *)((char*)ur + roff[r]); +} + +ulong * +reg(Ureg *ur, int r) +{ + return ®(ur, r); +} + +/* + * Ask if the instruction at EPC could have cause this badvaddr + */ +int +tstbadvaddr(Ureg *ur) +{ + int rn; + ulong iw, off, ea; + + iw = ur->pc; + if(ur->cause & BD) + iw += 4; + + if(seg(up, iw, 0) == 0) + return 0; + + iw = *(ulong*)iw; + +/* print("iw: %#lux\n", iw); /**/ + + switch((iw>>26) & 0x3f) { + default: + return 1; + case 0x20: /* LB */ + case 0x24: /* LBU */ + /* LD */ + case 0x35: + case 0x36: + case 0x37: /* LDCz */ + case 0x1A: /* LDL */ + case 0x1B: /* LDR */ + case 0x21: /* LH */ + case 0x25: /* LHU */ + case 0x30: /* LL */ + case 0x34: /* LLD */ + case 0x23: /* LW */ + case 0x31: + case 0x32: /* LWCz possible 0x33 */ + case 0x27: /* LWU */ + case 0x22: /* LWL */ + case 0x26: /* LWR */ + break; + + case 0x28: /* SB */ + case 0x38: /* SC */ + case 0x3C: /* SCD */ + case 0x3D: + case 0x3E: + case 0x3F: /* SDCz */ + case 0x2C: /* SDL */ + case 0x2D: /* SDR */ + case 0x29: /* SH */ + case 0x2B: /* SW */ + case 0x39: + case 0x3A: /* SWCz */ + case 0x2A: /* SWL */ + case 0x2E: /* SWR */ + break; + } + + off = iw & 0xffff; + if(off & 0x8000) + off |= ~0xffff; + + rn = (iw>>21) & 0x1f; + ea = *reg(ur, rn); + if(rn == 0) + ea = 0; + ea += off; + + /* print("ea %#lux %#lux(R%d) bv %#lux pc %#lux\n", ea, off, rn, ur->badvaddr, ur->pc); /**/ + + if(ur->badvaddr == ea) + return 0; + + return 1; +} + +/* + * we think we get consecutive page faults from unlucky combinations of + * scheduling and stlb hashes, and they only happen with 16K pages. + * however, we also get page faults while servicing the exact same fault. + * more than 5 consecutive faults is unusual, now that we have a better + * hash function. + * + * this can be helpful during mmu and cache debugging. + */ +static int +ckfaultstuck(Ureg *ur, int read, int code) +{ + uintptr pc, va; + + va = ur->badvaddr; + pc = ur->pc; + if (va != lflt.va || up->pid != lflt.pid || pc != lflt.pc || + code != lflt.code) { + /* at least one address or cause is different from last time */ + lflt.cnt = 1; + lflt.va = va; + lflt.pid = up->pid; + lflt.pc = pc; + lflt.code = code; + return 0; + } + ++lflt.cnt; + if (lflt.cnt >= 1000) /* fixfault() isn't fixing underlying cause? */ + panic("fault: %d consecutive faults for va %#p", lflt.cnt, va); + if (lflt.cnt > maxflt.cnt) { + maxflt.cnt = lflt.cnt; + maxflt.va = va; + maxflt.pid = up->pid; + maxflt.pc = pc; + kstrdup(&maxflt.prog, up->text); + } + + /* we're servicing that fault now! */ + /* adjust the threshold and program name to suit */ + if (lflt.cnt < 5 || strncmp(up->text, "8l", 2) != 0) + return 0; + iprint("%d consecutive faults for va %#p at pc %#p in %s " + "pid %ld\n", lflt.cnt, lflt.va, pc, up->text, lflt.pid); + iprint("\t%s: %s%s r31 %#lux tlbvirt %#lux\n", + excname[code], va == pc? "[instruction] ": "", + (read? "read": "write"), ur->r31, tlbvirt()); + return 0; +} + +char * +faultsprint(char *p, char *ep) +{ + if (Debug) + p = seprint(p, ep, + "max consecutive faults %d for va %#p in %s\n", + maxflt.cnt, maxflt.va, maxflt.prog); + return p; +} + +/* + * find out fault address and type of access. + * Call common fault handler. + */ +void +faultmips(Ureg *ur, int user, int code) +{ + int read; + ulong addr; + char *p, buf[ERRMAX]; + static int infault, printed; + + if (0 && infault && !printed) { + printed = 1; + print("fault: recursive fault (%d deep) pc %#p va %#p\n", + infault+1, ur->pc, ur->badvaddr); + } + infault++; + if(waserror()){ + infault--; + nexterror(); + } + + addr = ur->badvaddr; + addr &= ~(BY2PG-1); + + read = !(code==CTLBM || code==CTLBS); + +/* print("fault: %s code %d va %#p pc %#p r31 %#lux tlbvirt %#lux\n", + up->text, code, ur->badvaddr, ur->pc, ur->r31, tlbvirt());/**/ + + if (Debug && ckfaultstuck(ur, read, code) || fault(addr, read) == 0){ + infault--; + poperror(); + return; + } + + infault--; + poperror(); + + if(tstbadvaddr(ur)) { + print("fault: spurious badvaddr %#lux in %s at pc %#lux\n", + ur->badvaddr, up->text, ur->pc);/**/ + return; + } + + if(user) { + p = "store"; + if(read) + p = "load"; + snprint(buf, sizeof buf, "sys: trap: fault %s addr=%#lux r31=%#lux", + p, ur->badvaddr, ur->r31); + postnote(up, 1, buf, NDebug); + return; + } + + print("kernel %s vaddr=%#lux\n", excname[code], ur->badvaddr); + print("st=%#lux pc=%#lux r31=%#lux sp=%#lux\n", + ur->status, ur->pc, ur->r31, ur->sp); + dumpregs(ur); + panic("fault"); +} + +/* + * called in syscallfmt.c, sysfile.c, sysproc.c + */ +void +validalign(uintptr addr, unsigned align) +{ + /* + * Plan 9 is a 32-bit O/S, and the hardware it runs on + * does not usually have instructions which move 64-bit + * quantities directly, synthesizing the operations + * with 32-bit move instructions. Therefore, the compiler + * (and hardware) usually only enforce 32-bit alignment, + * if at all. + * + * Take this out if the architecture warrants it. + */ + if(align == sizeof(vlong)) + align = sizeof(long); + + /* + * Check align is a power of 2, then addr alignment. + */ + if((align != 0 && !(align & (align-1))) && !(addr & (align-1))) + return; + postnote(up, 1, "sys: odd address", NDebug); + error(Ebadarg); + /*NOTREACHED*/ +} --- /dev/null +++ /sys/src/9/loongson/fns.h @@ -0,0 +1,160 @@ +#include "../port/portfns.h" + +void arginit(void); +int busprobe(ulong); +ulong cankaddr(ulong); +void cleancache(void); +void clearmmucache(void); +void clock(Ureg*); +void clockinit(void); +void clockshutdown(void); +int cmpswap(long*, long, long); +void coherence(void); +void cycles(uvlong *); +void dcflush(void*, ulong); +void dcinvalid(void*, ulong); +void dumptlb(void); +void faultmips(Ureg*, int, int); +void* fbinit(void); +ulong fcr31(void); +void firmware(int); +void fpclear(void); +void fpsave(FPsave *); +void fptrap(Ureg*); +void fpwatch(Ureg *); +ulong getcause(void); +char* getconf(char*); +ulong getconfig(void); +ulong getpagemask(void); +ulong getstatus(void); +int gettlbp(ulong, ulong*); +ulong gettlbvirt(int); +void gettlbx(int, Softtlb*); +void gotopc(ulong); +void hinv(void); +int i8042auxcmd(int); +int i8042auxcmds(uchar*, int); +void i8042auxenable(void (*)(int, int)); +void i8042reset(void); +int i8250console(void); +int i8259disable(int); +int i8259enable(int); +void i8259init(void); +int i8259intack(void); +int i8259isr(int); +void icflush(void *, ulong); +void idle(void); +#define idlehands() /* no wait instruction, do nothing */ +#define inb(r) (*(uchar*)(IOBASE+(r))) +void insb(int, void*, int); +#define ins(r) (*(ushort*)(IOBASE+(r))) +void inss(int, void*, int); +#define inl(r) (*(ulong*)(IOBASE+(r))) +void insl(int, void*, int); +void intrenable(int, void(*)(Ureg*, void *), void *, int); +void introff(int); +void intron(int); +void intrshutdown(void); +void ioinit(void); +int isaconfig(char*, int, ISAConf*); +void kbdinit(void); +void kfault(Ureg*); +KMap* kmap(Page*); +void kmapinit(void); +void kmapinval(void); +void kunmap(KMap*); +void launchinit(void); +void launch(int); +void links(void); +ulong machstatus(void); +void newstart(void); +int newtlbpid(Proc*); +void online(void); +#define outb(r, v) (*(uchar*)(IOBASE+(r)) = v) +void outsb(int, void*, int); +#define outs(r, v) (*(ushort*)(IOBASE+(r)) = v) +void outss(int, void*, int); +#define outl(r, v) (*(ulong*)(IOBASE+(r)) = v) +void outsl(int, void*, int); +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 pcisubirq(int); +ulong prid(void); +void procrestore(Proc *); +void procsave(Proc *); +#define procsetup(p) ((p)->fpstate = FPinit) +void purgetlb(int); +Softtlb* putstlb(ulong, ulong); +int puttlb(ulong, ulong, ulong); +void puttlbx(int, ulong, ulong, ulong, int); +ulong rdcompare(void); +ulong rdcount(void); +ulong* reg(Ureg*, int); +void restfpregs(FPsave*, ulong); +void screeninit(void); +void setleveldest(int, int, uvlong*); +void setpagemask(ulong); +void setsp(ulong); +void setstatus(ulong); +void setwatchhi0(ulong); +void setwatchlo0(ulong); +void setwired(ulong); +ulong stlbhash(ulong); +void swcursorinit(void); +void syncclock(void); +long syscall(Ureg*); +void syscallfmt(int syscallno, ulong pc, va_list list); +void sysretfmt(int syscallno, va_list list, long ret, uvlong start, uvlong stop); +int tas(ulong*); +void tlbinit(void); +ulong tlbvirt(void); +void touser(uintptr); +void unleash(void); +#define userureg(ur) ((ur)->status & KUSER) +void validalign(uintptr, unsigned); +void vecinit(void); +void vector0(void); +void vector100(void); +void vector180(void); +void wrcompare(ulong); +void wrcount(ulong); +ulong wiredpte(vlong); +void machwire(void); +void _uartputs(char*, int); +int _uartprint(char*, ...); + +#define PTR2UINT(p) ((uintptr)(p)) +#define UINT2PTR(i) ((void*)(i)) + +#define waserror() (up->nerrlab++, setlabel(&up->errlab[up->nerrlab-1])) + +#define KADDR(a) ((void*)((ulong)(a)|KSEG0)) +#define PADDR(a) ((ulong)(a)&~KSEGM) + +#define KSEG0ADDR(a) ((void*)(((ulong)(a)&~KSEGM)|KSEG0)) +#define KSEG1ADDR(a) ((void*)((ulong)(a)|KSEG1)) + +#define sdmalloc(n) mallocalign(n, CACHELINESZ, 0, 0) +#define sdfree(p) free(p) + +int pmonprint(char*, ...); --- /dev/null +++ /sys/src/9/loongson/i8259.c @@ -0,0 +1,213 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +/* + * 8259 interrupt controllers + */ +enum +{ + Int0ctl= 0x20, /* control port (ICW1, OCW2, OCW3) */ + Int0aux= 0x21, /* everything else (ICW2, ICW3, ICW4, OCW1) */ + Int1ctl= 0xA0, /* control port */ + Int1aux= 0xA1, /* everything else (ICW2, ICW3, ICW4, OCW1) */ + + Icw1= 0x10, /* select bit in ctl register */ + Ocw2= 0x00, + Ocw3= 0x08, + + EOI= 0x20, /* non-specific end of interrupt */ + + Elcr1= 0x4D0, /* Edge/Level Triggered Register */ + Elcr2= 0x4D1, +}; + +static Lock i8259lock; +static int i8259mask = 0xFFFF; /* disabled interrupts */ +int i8259elcr; /* mask of level-triggered interrupts */ + +void +i8259init(void) +{ + int x; + uchar dummy; + + ilock(&i8259lock); + + /* + * Set up the first 8259 interrupt processor. + * Make 8259 interrupts start at CPU vector VectorPIC. + * Set the 8259 as master with edge triggered + * input with fully nested interrupts. + */ + outb(Int0ctl, (1<<4)|(0<<3)|(1<<0)); /* ICW1 - master, edge triggered, + ICW4 will be sent */ + outb(Int0aux, VectorPIC); /* ICW2 - interrupt vector offset */ + outb(Int0aux, 0x04); /* ICW3 - have slave on level 2 */ + outb(Int0aux, 0x01); /* ICW4 - 8086 mode, not buffered */ + + /* + * Set up the second 8259 interrupt processor. + * Make 8259 interrupts start at CPU vector VectorPIC+8. + * Set the 8259 as slave with edge triggered + * input with fully nested interrupts. + */ + outb(Int1ctl, (1<<4)|(0<<3)|(1<<0)); /* ICW1 - master, edge triggered, + ICW4 will be sent */ + outb(Int1aux, VectorPIC+8); /* ICW2 - interrupt vector offset */ + outb(Int1aux, 0x02); /* ICW3 - I am a slave on level 2 */ + outb(Int1aux, 0x01); /* ICW4 - 8086 mode, not buffered */ + outb(Int1aux, (i8259mask>>8) & 0xFF); + + /* + * pass #2 8259 interrupts to #1 + */ + i8259mask &= ~0x04; + outb(Int0aux, i8259mask & 0xFF); + + /* + * Set Ocw3 to return the ISR when ctl read. + * After initialisation status read is set to IRR. + * Read IRR first to possibly deassert an outstanding + * interrupt. + */ + dummy = inb(Int0ctl); + outb(Int0ctl, Ocw3|0x03); + USED(dummy); + dummy = inb(Int1ctl); + outb(Int1ctl, Ocw3|0x03); + USED(dummy); + + /* + * Check for Edge/Level register. + * This check may not work for all chipsets. + * First try a non-intrusive test - the bits for + * IRQs 13, 8, 2, 1 and 0 must be edge (0). If + * that's OK try a R/W test. + */ + x = (inb(Elcr2)<<8)|inb(Elcr1); + if(!(x & 0x2107)){ + outb(Elcr1, 0); + if(inb(Elcr1) == 0){ + outb(Elcr1, 0x20); + if(inb(Elcr1) == 0x20) + i8259elcr = x; + outb(Elcr1, x & 0xFF); + print("i8259: ELCR: %4.4uX\n", i8259elcr); + } + } + iunlock(&i8259lock); +} + +int +i8259isr(int vno) +{ + int irq, isr; + + if(vno < VectorPIC || vno > VectorPIC+MaxIrqPIC) + return 0; + irq = vno-VectorPIC; + + /* + * tell the 8259 that we're done with the + * highest level interrupt (interrupts are still + * off at this point) + */ + ilock(&i8259lock); + isr = inb(Int0ctl); + outb(Int0ctl, EOI); + if(irq >= 8){ + isr |= inb(Int1ctl)<<8; + outb(Int1ctl, EOI); + } + iunlock(&i8259lock); + + return isr & (1< MaxIrqPIC){ + print("i8259enable: irq %d out of range\n", irq); + return -1; + } + irqbit = 1<>8) & 0xFF); + +// if(i8259elcr & irqbit) +// v->eoi = i8259isr; +// else +// v->isr = i8259isr; + iunlock(&i8259lock); + + return VectorPIC+irq; +} + +int +i8259intack(void) +{ + int irq; + + outb(Int0ctl, Ocw3|0x07); /* intr ack on first 8259 */ + irq = inb(Int0ctl) & 7; + if(irq == 2) { /* cascade */ + outb(Int1ctl, Ocw3|0x07); /* intr ack on second 8259 */ + irq = (inb(Int1ctl) & 7) + 8; + } + return irq+VectorPIC; +} + +int +i8259vecno(int irq) +{ + return VectorPIC+irq; +} + +int +i8259disable(int irq) +{ + int irqbit; + + /* + * Given an IRQ, disable the corresponding interrupt + * in the 8259. + */ + if(irq < 0 || irq > MaxIrqPIC){ + print("i8259disable: irq %d out of range\n", irq); + return -1; + } + irqbit = 1<>8) & 0xFF); + } + iunlock(&i8259lock); + return 0; +} --- /dev/null +++ /sys/src/9/loongson/init9.s @@ -0,0 +1,11 @@ +#include "spim.s" + +TEXT _main(SB), $8 + MOVW $setR30(SB), R30 + + MOVW $boot(SB), R1 + ADDU $12, R29, R2 /* get a pointer to 0(FP) */ + MOVW R1, 4(R29) + MOVW R2, 8(R29) + + JMP startboot(SB) --- /dev/null +++ /sys/src/9/loongson/initreboot.s @@ -0,0 +1,48 @@ +/* + * minimal reboot code + */ +#include "mem.h" +#include "spim.s" + +TEXT _main(SB), $0 + MOVW $setR30(SB), R30 + JMP main(SB) + +TEXT setsp(SB), $-4 + MOVW R1, R29 + RET + +TEXT coherence(SB), $-4 + SYNC + RET + +TEXT go(SB), $-4 /* go(kernel) */ + MOVW _argc(SB), R4 + CONST (CONFARGV, R5) + MOVW _env(SB), R6 + MOVW R0, R7 + JMP (R1) + + JMP 0(PC) + +/* + * cache manipulation + */ +TEXT cleancache(SB), $-4 + MOVW M(STATUS), R10 /* old status -> R10 */ + MOVW R0, M(STATUS) /* intrs off */ + EHB + MOVW $KSEG0, R1 /* index, not address, kseg0 avoids tlb */ + MOVW $(SCACHESIZE/4), R9 /* 4-way cache */ +ccache: + CACHE SD+IWBI, 0(R1) /* flush & invalidate thru L2 by index */ + CACHE SD+IWBI, 1(R1) /* ways are least significant bits */ + CACHE SD+IWBI, 2(R1) + CACHE SD+IWBI, 3(R1) + SUBU $CACHELINESZ, R9 + ADDU $CACHELINESZ, R1 + BGTZ R9, ccache + SYNC + MOVW R10, M(STATUS) + EHB + RET --- /dev/null +++ /sys/src/9/loongson/io.h @@ -0,0 +1,333 @@ +enum { + Mhz = 1000*1000, +}; + +/* + * duarts, frequency and registers + */ +#define DUARTFREQ 1843200 + +/* + * interrupt levels on CPU boards. + */ +enum +{ + ILmin = 2, + ILpci = 2, + ILunused3 = 3, + ILduart0 = 4, + IL8259 = 5, + ILperf = 6, + ILclock = 7, + ILmax = 7, + + ILshift = 8, +}; + +/* + * PCI configuration regesters and interrupt bits + */ +#define Reset (ulong*)(PCICFG+0x104) +#define Rstcpucold (1<<2) /* cpu cold reset */ + +#define Pcimapcfg (ulong*)(PCICFG+0x118) +#define Pciintrsts (ulong*)(PCICFG+0x13c) +#define Pciintren (ulong*)(PCICFG+0x138) +#define Pciintrenset (ulong*)(PCICFG+0x130) +#define Pciintrenclr (ulong*)(PCICFG+0x134) + +#define Pciintrbase 25 + +/* + * i8259 interrupts + */ +enum { + IrqCLOCK = 0, + IrqKBD = 1, + IrqUART1 = 3, + IrqUART0 = 4, + IrqPCMCIA = 5, + IrqFLOPPY = 6, + IrqLPT = 7, + IrqIRQ7 = 7, + IrqAUX = 12, /* PS/2 port */ + IrqIRQ13 = 13, /* coprocessor on 386 */ + IrqATA0 = 14, + IrqATA1 = 15, + MaxIrqPIC = 15, + + VectorPIC = 0, + MaxVectorPIC = VectorPIC+MaxIrqPIC, +}; + + +/* + * mostly PCI from here on + */ + +typedef struct Pcisiz Pcisiz; +typedef struct Pcidev Pcidev; +typedef struct Vctl Vctl; + +struct Vctl { + Vctl* next; /* handlers on this vector */ + + char name[KNAMELEN]; /* of driver */ + int isintr; /* interrupt or fault/trap */ + int irq; + int tbdf; + int (*isr)(int); /* get isr bit for this irq */ + int (*eoi)(int); /* eoi */ + + void (*f)(Ureg*, void*); /* handler to call */ + void* a; /* argument to call it with */ +}; + +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 */ +}; + +#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) +#define BUSUNKNOWN (-1) + +enum { + MaxEISA = 16, + CfgEISA = 0xC80, +}; + +/* + * 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 */ + + PciBAR0 = 0x10, /* base address */ + PciBAR1 = 0x14, + + PciINTL = 0x3C, /* interrupt line */ + PciINTP = 0x3D, /* interrupt pin */ +}; + +/* 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) */ + Pciscsata = 6, /* SATA */ + + /* 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 */ +}; + +struct Pcisiz +{ + Pcidev* dev; + int siz; + int bar; +}; + +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 */ + uchar intp; /* interrupt pin */ + + 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 */ +}; + +enum { + /* vendor ids */ + Vatiamd = 0x1002, + Vintel = 0x8086, + Vjmicron= 0x197b, + Vmarvell= 0x1b4b, + Vmyricom= 0x14c1, + Vrtl = 0x10ec, + Vsm = 0x126f, +}; + +#define PCIWINDOW 0x80000000 +#define PCIWADDR(va) (PADDR(va)+PCIWINDOW) +#define ISAWINDOW 0 +#define ISAWADDR(va) (PADDR(va)+ISAWINDOW) + +#define PCIMEMADDR(pa) ((pa)|PCIMEM) + +/* SMBus transactions */ +enum +{ + SMBquick, /* sends address only */ + + /* write */ + SMBsend, /* sends address and cmd */ + SMBbytewrite, /* sends address and cmd and 1 byte */ + SMBwordwrite, /* sends address and cmd and 2 bytes */ + + /* read */ + SMBrecv, /* sends address, recvs 1 byte */ + SMBbyteread, /* sends address and cmd, recv's byte */ + SMBwordread, /* sends address and cmd, recv's 2 bytes */ +}; + +typedef struct SMBus SMBus; +struct SMBus { + QLock; /* mutex */ + Rendez r; /* rendezvous point for completion interrupts */ + void *arg; /* implementation dependent */ + ulong base; /* port or memory base of smbus */ + int busy; + void (*transact)(SMBus*, int, int, int, uchar*); +}; + +#pragma varargck type "T" int +#pragma varargck type "T" uint --- /dev/null +++ /sys/src/9/loongson/kbd.c @@ -0,0 +1,693 @@ +/* + * keyboard input + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +enum { + Data= 0x60, /* data port */ + + Status= 0x64, /* status port */ + Inready= 0x01, /* input character ready */ + Outbusy= 0x02, /* output busy */ + Sysflag= 0x04, /* system flag */ + Cmddata= 0x08, /* cmd==0, data==1 */ + Inhibit= 0x10, /* keyboard/mouse inhibited */ + Minready= 0x20, /* mouse character ready */ + Rtimeout= 0x40, /* general timeout */ + Parity= 0x80, + + Cmd= 0x64, /* command port (write only) */ + + Spec= 0xF800, /* Unicode private space */ + PF= Spec|0x20, /* num pad function key */ + View= Spec|0x00, /* view (shift window up) */ + KF= 0xF000, /* function key (begin Unicode private space) */ + Shift= Spec|0x60, + Break= Spec|0x61, + Ctrl= Spec|0x62, + Latin= Spec|0x63, + Caps= Spec|0x64, + Num= Spec|0x65, + Middle= Spec|0x66, + Altgr= Spec|0x67, + Kmouse= Spec|0x100, + No= 0x00, /* peter */ + + Home= KF|13, + Up= KF|14, + Pgup= KF|15, + Print= KF|16, + Left= KF|17, + Right= KF|18, + End= KF|24, + Down= View, + Pgdown= KF|19, + Ins= KF|20, + Del= 0x7F, + Scroll= KF|21, + + Nscan= 128, + + Int= 0, /* kbscans indices */ + Ext, + Nscans, +}; + +/* + * The codes at 0x79 and 0x7b are produced by the PFU Happy Hacking keyboard. + * A 'standard' keyboard doesn't produce anything above 0x58. + */ +Rune kbtab[Nscan] = +{ +[0x00] No, 0x1b, '1', '2', '3', '4', '5', '6', +[0x08] '7', '8', '9', '0', '-', '=', '\b', '\t', +[0x10] 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', +[0x18] 'o', 'p', '[', ']', '\n', Ctrl, 'a', 's', +[0x20] 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', +[0x28] '\'', '`', Shift, '\\', 'z', 'x', 'c', 'v', +[0x30] 'b', 'n', 'm', ',', '.', '/', Shift, '*', +[0x38] Latin, ' ', Ctrl, KF|1, KF|2, KF|3, KF|4, KF|5, +[0x40] KF|6, KF|7, KF|8, KF|9, KF|10, Num, Scroll, '7', +[0x48] '8', '9', '-', '4', '5', '6', '+', '1', +[0x50] '2', '3', '0', '.', No, No, No, KF|11, +[0x58] KF|12, No, No, No, No, No, No, No, +[0x60] No, No, No, No, No, No, No, No, +[0x68] No, No, No, No, No, No, No, No, +[0x70] No, No, No, No, No, No, No, No, +[0x78] No, View, No, Up, No, No, No, No, +}; + +Rune kbtabshift[Nscan] = +{ +[0x00] No, 0x1b, '!', '@', '#', '$', '%', '^', +[0x08] '&', '*', '(', ')', '_', '+', '\b', '\t', +[0x10] 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', +[0x18] 'O', 'P', '{', '}', '\n', Ctrl, 'A', 'S', +[0x20] 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', +[0x28] '"', '~', Shift, '|', 'Z', 'X', 'C', 'V', +[0x30] 'B', 'N', 'M', '<', '>', '?', Shift, '*', +[0x38] Latin, ' ', Ctrl, KF|1, KF|2, KF|3, KF|4, KF|5, +[0x40] KF|6, KF|7, KF|8, KF|9, KF|10, Num, Scroll, '7', +[0x48] '8', '9', '-', '4', '5', '6', '+', '1', +[0x50] '2', '3', '0', '.', No, No, No, KF|11, +[0x58] KF|12, No, No, No, No, No, No, No, +[0x60] No, No, No, No, No, No, No, No, +[0x68] No, No, No, No, No, No, No, No, +[0x70] No, No, No, No, No, No, No, No, +[0x78] No, Up, No, Up, No, No, No, No, +}; + +Rune kbtabesc1[Nscan] = +{ +[0x00] No, No, No, No, No, No, No, No, +[0x08] No, No, No, No, No, No, No, No, +[0x10] No, No, No, No, No, No, No, No, +[0x18] No, No, No, No, '\n', Ctrl, No, No, +[0x20] No, No, No, No, No, No, No, No, +[0x28] No, No, Shift, No, No, No, No, No, +[0x30] No, No, No, No, No, '/', No, Print, +[0x38] Altgr, No, No, No, No, No, No, No, +[0x40] No, No, No, No, No, No, Break, Home, +[0x48] Up, Pgup, No, Left, No, Right, No, End, +[0x50] Down, Pgdown, Ins, Del, No, No, No, No, +[0x58] No, No, No, No, No, No, No, No, +[0x60] No, No, No, No, No, No, No, No, +[0x68] No, No, No, No, No, No, No, No, +[0x70] No, No, No, No, No, No, No, No, +[0x78] No, Up, No, No, No, No, No, No, +}; + +Rune kbtabaltgr[Nscan] = +{ +[0x00] No, No, No, No, No, No, No, No, +[0x08] No, No, No, No, No, No, No, No, +[0x10] No, No, No, No, No, No, No, No, +[0x18] No, No, No, No, '\n', Ctrl, No, No, +[0x20] No, No, No, No, No, No, No, No, +[0x28] No, No, Shift, No, No, No, No, No, +[0x30] No, No, No, No, No, '/', No, Print, +[0x38] Altgr, No, No, No, No, No, No, No, +[0x40] No, No, No, No, No, No, Break, Home, +[0x48] Up, Pgup, No, Left, No, Right, No, End, +[0x50] Down, Pgdown, Ins, Del, No, No, No, No, +[0x58] No, No, No, No, No, No, No, No, +[0x60] No, No, No, No, No, No, No, No, +[0x68] No, No, No, No, No, No, No, No, +[0x70] No, No, No, No, No, No, No, No, +[0x78] No, Up, No, No, No, No, No, No, +}; + +Rune kbtabctrl[Nscan] = +{ +[0x00] No, '', '', '', '', '', '', '', +[0x08] '', '', '', '', ' ', '', '\b', '\t', +[0x10] '', '', '', '', '', '', '', '\t', +[0x18] '', '', '', '', '\n', Ctrl, '', '', +[0x20] '', '', '', '\b', '\n', ' ', ' ', '', +[0x28] '', No, Shift, '', '', '', '', '', +[0x30] '', '', ' ', ' ', '', '', Shift, '\n', +[0x38] Latin, No, Ctrl, '', '', '', '', '', +[0x40] '', '', ' ', ' ', '', '', '', '', +[0x48] '', '', ' ', '', '', '', ' ', '', +[0x50] '', '', '', '', No, No, No, '', +[0x58] ' ', No, No, No, No, No, No, No, +[0x60] No, No, No, No, No, No, No, No, +[0x68] No, No, No, No, No, No, No, No, +[0x70] No, No, No, No, No, No, No, No, +[0x78] No, '', No, '\b', No, No, No, No, +}; + +enum +{ + /* controller command byte */ + Cscs1= (1<<6), /* scan code set 1 */ + Cauxdis= (1<<5), /* mouse disable */ + Ckbddis= (1<<4), /* kbd disable */ + Csf= (1<<2), /* system flag */ + Cauxint= (1<<1), /* mouse interrupt enable */ + Ckbdint= (1<<0), /* kbd interrupt enable */ +}; + +int mouseshifted; +void (*kbdmouse)(int); + +static Lock i8042lock; +static uchar ccc; +static void (*auxputc)(int, int); +static int nokbd = 1; /* flag: no PS/2 keyboard */ + +/* + * wait for output no longer busy + */ +static int +outready(void) +{ + int tries; + + for(tries = 0; (inb(Status) & Outbusy); tries++){ + if(tries > 500) + return -1; + delay(2); + } + return 0; +} + +/* + * wait for input + */ +static int +inready(void) +{ + int tries; + + for(tries = 0; !(inb(Status) & Inready); tries++){ + if(tries > 500) + return -1; + delay(2); + } + return 0; +} + +int +i8042auxcmd(int cmd) +{ + unsigned int c; + int tries; + static int badkbd; + + if(badkbd) + return -1; + c = 0; + tries = 0; + + ilock(&i8042lock); + do{ + if(tries++ > 2) + break; + if(outready() < 0) + break; + outb(Cmd, 0xD4); + if(outready() < 0) + break; + outb(Data, cmd); + if(outready() < 0) + break; + if(inready() < 0) + break; + c = inb(Data); + } while(c == 0xFE || c == 0); + iunlock(&i8042lock); + + if(c != 0xFA){ + print("i8042: %2.2ux returned to the %2.2ux command\n", c, cmd); + badkbd = 1; /* don't keep trying; there might not be one */ + return -1; + } + return 0; +} + +int +i8042auxcmds(uchar *cmd, int ncmd) +{ + int i; + + ilock(&i8042lock); + for(i=0; inum) + leds |= 1<<1; + if(0 && kbscan->caps) /* we don't implement caps lock */ + leds |= 1<<2; + + ilock(&i8042lock); + outready(); + outb(Data, 0xed); /* `reset keyboard lock states' */ + if(inready() == 0) + dummy = inb(Data); + + outready(); + outb(Data, leds); + if(inready() == 0) + dummy = inb(Data); + + outready(); + iunlock(&i8042lock); + USED(dummy); +} + +/* + * Scan code processing + */ +void +kbdputsc(int c, int external) +{ + int i, keyup; + Kbscan *kbscan; + + if(external) + kbscan = &kbscans[Ext]; + else + kbscan = &kbscans[Int]; + + if(kdebug) + print("sc %x ms %d\n", c, mouseshifted); + /* + * e0's is the first of a 2 character sequence, e1 the first + * of a 3 character sequence (on the safari) + */ + if(c == 0xe0){ + kbscan->esc1 = 1; + return; + } else if(c == 0xe1){ + kbscan->esc2 = 2; + return; + } + + keyup = c & 0x80; + c &= 0x7f; + if(c > sizeof kbtab){ + c |= keyup; + if(c != 0xFF) /* these come fairly often: CAPSLOCK U Y */ + print("unknown key %ux\n", c); + return; + } + + if(kbscan->esc1){ + c = kbtabesc1[c]; + kbscan->esc1 = 0; + } else if(kbscan->esc2){ + kbscan->esc2--; + return; + } else if(kbscan->shift) + c = kbtabshift[c]; + else if(kbscan->altgr) + c = kbtabaltgr[c]; + else if(kbscan->ctl) + c = kbtabctrl[c]; + else + c = kbtab[c]; + + if(kbscan->caps && c<='z' && c>='a') + c += 'A' - 'a'; + + /* + * keyup only important for shifts + */ + if(keyup){ + switch(c){ + case Latin: + kbscan->alt = 0; + break; + case Shift: + kbscan->shift = 0; + mouseshifted = 0; + if(kdebug) + print("shiftclr\n"); + break; + case Ctrl: + kbscan->ctl = 0; + break; + case Altgr: + kbscan->altgr = 0; + break; + case Kmouse|1: + case Kmouse|2: + case Kmouse|3: + case Kmouse|4: + case Kmouse|5: + kbscan->buttons &= ~(1<<(c-Kmouse-1)); + if(kbdmouse) + kbdmouse(kbscan->buttons); + break; + } + return; + } + + /* + * normal character + */ + if(!(c & (Spec|KF))){ + if(kbscan->ctl) + if(kbscan->alt && c == Del) + exit(0); + if(!kbscan->collecting){ + kbdputc(kbdq, c); + return; + } + kbscan->kc[kbscan->nk++] = c; + c = latin1(kbscan->kc, kbscan->nk); + if(c < -1) /* need more keystrokes */ + return; + if(c != -1) /* valid sequence */ + kbdputc(kbdq, c); + else /* dump characters */ + for(i=0; ink; i++) + kbdputc(kbdq, kbscan->kc[i]); + kbscan->nk = 0; + kbscan->collecting = 0; + return; + } else { + switch(c){ + case Caps: + kbscan->caps ^= 1; + return; + case Num: + kbscan->num ^= 1; + if(!external) + setleds(kbscan); + return; + case Shift: + kbscan->shift = 1; + if(kdebug) + print("shift\n"); + mouseshifted = 1; + return; + case Latin: + kbscan->alt = 1; + /* + * VMware and Qemu use Ctl-Alt as the key combination + * to make the VM give up keyboard and mouse focus. + * This has the unfortunate side effect that when you + * come back into focus, Plan 9 thinks you want to type + * a compose sequence (you just typed alt). + * + * As a clumsy hack around this, we look for ctl-alt + * and don't treat it as the start of a compose sequence. + */ + if(!kbscan->ctl){ + kbscan->collecting = 1; + kbscan->nk = 0; + } + return; + case Ctrl: + kbscan->ctl = 1; + return; + case Altgr: + kbscan->altgr = 1; + return; + case Kmouse|1: + case Kmouse|2: + case Kmouse|3: + case Kmouse|4: + case Kmouse|5: + kbscan->buttons |= 1<<(c-Kmouse-1); + if(kbdmouse) + kbdmouse(kbscan->buttons); + return; + case KF|11: + print("kbd debug on, F12 turns it off\n"); + kdebug = 1; + break; + case KF|12: + kdebug = 0; + break; + } + } + kbdputc(kbdq, c); +} + +/* + * keyboard interrupt + */ +static void +i8042intr(Ureg*, void*) +{ + int s, c; + + /* + * get status + */ + ilock(&i8042lock); + s = inb(Status); + if(!(s&Inready)){ + iunlock(&i8042lock); + return; + } + + /* + * get the character + */ + c = inb(Data); + iunlock(&i8042lock); + + /* + * if it's the aux port... + */ + if(s & Minready){ + if(auxputc != nil) + auxputc(c, kbscans[Int].shift); + return; + } + + kbdputsc(c, Int); +} + +void +i8042auxenable(void (*putc)(int, int)) +{ + char *err = "i8042: aux init failed\n"; + + /* enable kbd/aux xfers and interrupts */ + ccc &= ~Cauxdis; + ccc |= Cauxint; + + ilock(&i8042lock); + if(outready() < 0) + print(err); + outb(Cmd, 0x60); /* write control register */ + if(outready() < 0) + print(err); + outb(Data, ccc); + if(outready() < 0) + print(err); + outb(Cmd, 0xA8); /* auxiliary device enable */ + if(outready() < 0){ + iunlock(&i8042lock); + return; + } + auxputc = putc; + intrenable(IL8259, i8042intr, 0, IrqAUX); + iunlock(&i8042lock); +} + +static char *initfailed = "i8042: kbdinit failed\n"; + +static int +outbyte(int port, int c) +{ + outb(port, c); + if(outready() < 0) { + print(initfailed); + return -1; + } + return 0; +} + +void +kbdinit(void) +{ + int c, try; + uchar dummy; + + SET(dummy); + kbdq = qopen(4*1024, 0, 0, 0); + if(kbdq == nil) + panic("kbdinit"); + qnoblock(kbdq, 1); + + /* wait for a quiescent controller */ + try = 500; + while(try-- > 0 && (c = inb(Status)) & (Outbusy | Inready)) { + if(c & Inready) + dummy = inb(Data); + delay(1); + } + if (try <= 0) { + print(initfailed); + return; + } + + /* get current controller command byte */ + outb(Cmd, 0x20); + if(inready() < 0){ + print("i8042: kbdinit can't read ccc\n"); + ccc = 0; + } else + ccc = inb(Data); + + /* enable kbd xfers and interrupts */ + ccc &= ~Ckbddis; + ccc |= Csf | Ckbdint | Cscs1; + if(outready() < 0) { + print(initfailed); + return; + } + + nokbd = 0; + + /* disable mouse */ + if (outbyte(Cmd, 0x60) < 0 || outbyte(Data, ccc) < 0) + print("i8042: kbdinit mouse disable failed\n"); + + /* see http://www.computer-engineering.org/ps2keyboard for codes */ + if(getconf("*typematic") != nil) + /* set typematic rate/delay (0 -> delay=250ms & rate=30cps) */ + if(outbyte(Data, 0xf3) < 0 || outbyte(Data, 0) < 0) + print("i8042: kbdinit set typematic rate failed\n"); + + intrenable(IL8259, i8042intr, 0, IrqKBD); + + kbscans[Int].num = 0; + setleds(&kbscans[Int]); + USED(dummy); +} + +void +kbdputmap(ushort m, ushort scanc, Rune r) +{ + if(scanc >= Nscan) + error(Ebadarg); + switch(m) { + default: + error(Ebadarg); + case 0: + kbtab[scanc] = r; + break; + case 1: + kbtabshift[scanc] = r; + break; + case 2: + kbtabesc1[scanc] = r; + break; + case 3: + kbtabaltgr[scanc] = r; + break; + case 4: + kbtabctrl[scanc] = r; + break; + } +} + +int +kbdgetmap(uint offset, int *t, int *sc, Rune *r) +{ + if ((int)offset < 0) + error(Ebadarg); + *t = offset/Nscan; + *sc = offset%Nscan; + switch(*t) { + default: + return 0; + case 0: + *r = kbtab[*sc]; + return 1; + case 1: + *r = kbtabshift[*sc]; + return 1; + case 2: + *r = kbtabesc1[*sc]; + return 1; + case 3: + *r = kbtabaltgr[*sc]; + return 1; + case 4: + *r = kbtabctrl[*sc]; + return 1; + } +} --- /dev/null +++ /sys/src/9/loongson/l.s @@ -0,0 +1,1041 @@ +#include "mem.h" +#include "spim.s" + +/* + * entrypoint. set SB, pass arguments to main(). + * PMON's calling convention: + * argc R4 + * argv R5 + * envp R6 + * callvec R7 + */ + +TEXT start(SB), $-4 + MOVW $setR30(SB), R30 + + PUTC('9', R1, R2) + + /* don't enable any interrupts, out of EXL mode */ + MOVW $CU1, R1 + MOVW R1, M(STATUS) + EHB + MOVW R0, M(CAUSE) + EHB + MOVW R0, M(COMPARE) + EHB + MOVW R0, M(PERFCTL) + EHB + + MOVW R4, _argc(SB) + MOVW R5, _argv(SB) + MOVW R6, _env(SB) + MOVW R7, pmon_callvec(SB) + + MOVW $(FPOVFL|FPZDIV|FPINVAL|FPFLUSH), R1 + MOVW R1, FCR31 // permit only inexact and underflow + NOP + MOVD $0.5, F26 + SUBD F26, F26, F24 + ADDD F26, F26, F28 + ADDD F28, F28, F30 + + MOVD F24, F0 + MOVD F24, F2 + MOVD F24, F4 + MOVD F24, F6 + MOVD F24, F8 + MOVD F24, F10 + MOVD F24, F12 + MOVD F24, F14 + MOVD F24, F16 + MOVD F24, F18 + MOVD F24, F20 + MOVD F24, F22 + + MOVW $TLBROFF, R1 + MOVW R1, M(WIRED) + EHB + MOVW R0, M(CONTEXT) + EHB + + /* set KSEG0 cachability before trying LL/SC in lock code */ + TOKSEG1(7) + MOVW M(CONFIG), R1 + AND $(~CFG_K0), R1 + /* make kseg0 cachable */ + OR $(PTECACHABILITY>>3), R1 + MOVW R1, M(CONFIG) + EHB + TOKSEG0(7) + + MOVW $MACHADDR, R(MACH) + ADDU $(MACHSIZE-BY2WD), R(MACH), R29 /* set up stack */ + + JAL main(SB) + + /* main() returned */; + PUTC('\r', R1, R2); DELAY(R2); PUTC('\n', R1, R2); DELAY(R2); + PUTC('m', R1, R2); DELAY(R2); PUTC('a', R1, R2); DELAY(R2); + PUTC('i', R1, R2); DELAY(R2); PUTC('n', R1, R2); DELAY(R2); + PUTC('(', R1, R2); DELAY(R2); PUTC(')', R1, R2); DELAY(R2); + PUTC(' ', R1, R2); DELAY(R2); PUTC('r', R1, R2); DELAY(R2); + PUTC('e', R1, R2); DELAY(R2); PUTC('t', R1, R2); DELAY(R2); + PUTC('u', R1, R2); DELAY(R2); PUTC('r', R1, R2); DELAY(R2); + PUTC('n', R1, R2); DELAY(R2); PUTC('e', R1, R2); DELAY(R2); + PUTC('d', R1, R2); DELAY(R2); PUTC('\r', R1, R2); DELAY(R2); + PUTC('\n', R1, R2); + JMP 0(PC) /* loop */ + +/* target for JALR in TOKSEG0/1 */ +TEXT ret0(SB), $-4 + AND $~KSEG1, R22 + OR $KSEG0, R22 + JMP (R22) + +TEXT ret1(SB), $-4 + OR $KSEG1, R22 + JMP (R22) + +/* print R1 in hex; clobbers R3—8 */ +TEXT printhex(SB), $-4 + MOVW $32, R5 + MOVW $9, R7 +prtop: + SUB $4, R5 + MOVW R1, R6 + SRL R5, R6 + AND $0xf, R6 + SGTU R6, R7, R8 + BEQ R8, prdec /* branch if R6 <= 9 */ + ADD $('a'-10), R6 + JMP prchar +prdec: + ADD $'0', R6 +prchar: + PUTC(R6, R3, R4) + BNE R5, prtop + RET + +/* + * Take first processor into user mode + * - argument is stack pointer to user + */ +TEXT touser(SB), $-4 + MOVW R1, R29 + MOVW $(UTZERO+32), R2 /* header appears in text */ + MOVW R0, M(CAUSE) + EHB + + MOVW M(STATUS), R4 + AND $(~KMODEMASK), R4 + OR $(KUSER|IE|EXL), R4 /* switch to user mode, intrs on, exc */ + MOVW R4, M(STATUS) + + MOVW R2, M(EPC) + ERET /* clears EXL */ + +TEXT _loop(SB), $-4 + MOVW M(STATUS), R1 + JAL printhex(SB) + JMP 0(PC) + +/* + * manipulate interrupts + */ + +/* enable an interrupt; bit is in R1 */ +TEXT intron(SB), $0 + MOVW M(STATUS), R2 + OR R1, R2 + MOVW R2, M(STATUS) + EHB + RET + +/* disable an interrupt; bit is in R1 */ +TEXT introff(SB), $0 + MOVW M(STATUS), R2 + XOR $-1, R1 + AND R1, R2 + MOVW R2, M(STATUS) + EHB + RET + +TEXT splhi(SB), $0 + MOVW R31, 12(R(MACH)) /* save PC in m->splpc */ + MOVW M(STATUS), R1 + EHB + AND $(~IE), R1, R2 + MOVW R2, M(STATUS) + EHB + RET + +TEXT splx(SB), $0 + MOVW R31, 12(R(MACH)) /* save PC in m->splpc */ + MOVW M(STATUS), R2 + EHB + AND $IE, R1 + AND $(~IE), R2 + OR R2, R1 + MOVW R1, M(STATUS) + EHB + RET + +TEXT spllo(SB), $0 + MOVW M(STATUS), R1 + EHB + OR $IE, R1, R2 + MOVW R2, M(STATUS) + EHB + RET + +TEXT spldone(SB), $0 + RET + +TEXT islo(SB), $0 + MOVW M(STATUS), R1 + AND $IE, R1 + RET + +TEXT coherence(SB), $-4 + SYNC + NOP + NOP + RET + +/* + * process switching + */ + +TEXT setlabel(SB), $-4 + MOVW R29, 0(R1) /* sp */ + MOVW R31, 4(R1) /* pc */ + MOVW R0, R1 + RET + +TEXT gotolabel(SB), $-4 + MOVW 0(R1), R29 /* sp */ + MOVW 4(R1), R31 /* pc */ + MOVW $1, R1 + RET + +TEXT machstatus(SB), $0 + MOVW M(STATUS), R1 + RET + +TEXT getstatus(SB), $0 + MOVW M(STATUS), R1 + RET + +TEXT setstatus(SB), $0 + MOVW R1, M(STATUS) + EHB + RET + +TEXT rdcount(SB), $0 + MOVW M(COUNT), R1 + RET + +TEXT wrcount(SB), $0 + MOVW R1, M(COUNT) + EHB + RET + +TEXT wrcompare(SB), $0 + MOVW R1, M(COMPARE) + EHB + RET + +TEXT rdcompare(SB), $0 + MOVW M(COMPARE), R1 + RET + +TEXT prid(SB), $0 + MOVW M(PRID), R1 + RET + +TEXT getconfig(SB), $0 + MOVW M(CONFIG), R1 + RET + +TEXT getcause(SB), $0 + MOVW M(CAUSE), R1 + RET + +/* + * the tlb routines need to be called at splhi. + */ + +TEXT puttlb(SB), $0 /* puttlb(virt, phys0, phys1) */ + MOVW R1, M(TLBVIRT) + EHB + MOVW 4(FP), R2 /* phys0 */ + MOVW 8(FP), R3 /* phys1 */ + MOVW R2, M(TLBPHYS0) + EHB + MOVW $PGSZ, R1 + MOVW R3, M(TLBPHYS1) + EHB + MOVW R1, M(PAGEMASK) + EHB + OR R2, R3, R4 + AND $PTEVALID, R4 + TLBP /* tlb probe */ + EHB + MOVW M(INDEX), R1 + BGEZ R1, index /* if tlb entry found, use it */ + BEQ R4, dont /* not valid? cf. kunmap */ + MOVW M(RANDOM), R1 /* write random tlb entry */ + MOVW R1, M(INDEX) + EHB +index: + TLBWI /* write indexed tlb entry */ + EHB +dont: + RET + +TEXT getwired(SB),$0 + MOVW M(WIRED), R1 + RET + +TEXT setwired(SB),$0 + MOVW R1, M(WIRED) + EHB + RET + +TEXT getrandom(SB),$0 + MOVW M(RANDOM), R1 + RET + +TEXT getpagemask(SB),$0 + MOVW M(PAGEMASK), R1 + RET + +TEXT setpagemask(SB),$0 + MOVW R1, M(PAGEMASK) + EHB + MOVW R0, R1 /* prevent accidents */ + RET + +TEXT puttlbx(SB), $0 /* puttlbx(index, virt, phys0, phys1, pagemask) */ + MOVW 4(FP), R2 + MOVW 8(FP), R3 + MOVW 12(FP), R4 + MOVW 16(FP), R5 + MOVW R2, M(TLBVIRT) + EHB + MOVW R3, M(TLBPHYS0) + EHB + MOVW R4, M(TLBPHYS1) + EHB + MOVW R5, M(PAGEMASK) + EHB + MOVW R1, M(INDEX) + EHB + TLBWI + EHB + RET + +TEXT tlbvirt(SB), $0 + MOVW M(TLBVIRT), R1 + RET + +TEXT gettlbvirt(SB), $0 /* gettlbvirt(index) */ + MOVW M(TLBVIRT), R10 /* save our asid */ + MOVW R1, M(INDEX) + EHB + TLBR /* read indexed tlb entry */ + EHB + MOVW M(TLBVIRT), R1 + MOVW R10, M(TLBVIRT) /* restore our asid */ + EHB + RET + +TEXT gettlbx(SB), $0 /* gettlbx(index, &entry) */ + MOVW 4(FP), R5 + MOVW M(TLBVIRT), R10 /* save our asid */ + MOVW R1, M(INDEX) + EHB + TLBR /* read indexed tlb entry */ + EHB + MOVW M(TLBVIRT), R2 + MOVW M(TLBPHYS0), R3 + MOVW M(TLBPHYS1), R4 + MOVW R2, 0(R5) + MOVW R3, 4(R5) + MOVW R4, 8(R5) + MOVW R10, M(TLBVIRT) /* restore our asid */ + EHB + RET + +TEXT gettlbp(SB), $0 /* gettlbp(tlbvirt, &entry) */ + MOVW 4(FP), R5 + MOVW M(TLBVIRT), R10 /* save our asid */ + MOVW R1, M(TLBVIRT) + EHB + TLBP /* probe tlb */ + EHB + MOVW M(INDEX), R1 + BLTZ R1, gettlbp1 /* if no tlb entry found, return */ + TLBR /* read indexed tlb entry */ + EHB + MOVW M(TLBVIRT), R2 + MOVW M(TLBPHYS0), R3 + MOVW M(TLBPHYS1), R4 + MOVW M(PAGEMASK), R6 + MOVW R2, 0(R5) + MOVW R3, 4(R5) + MOVW R4, 8(R5) + MOVW R6, 12(R5) +gettlbp1: + MOVW R10, M(TLBVIRT) /* restore our asid */ + EHB + RET + +/* + * exceptions. + * mips promises that there will be no current hazards upon entry + * to exception handlers. + */ + +/* vector at KSEG0+0x80, simple tlb refill */ +TEXT vector0(SB), $-4 + MOVW $utlbmiss(SB), R26 + JMP (R26) + +/* + * compute stlb hash index. + * must match index calculation in mmu.c/putstlb() + * + * M(TLBVIRT) [page & asid] in arg, result in arg. + * stir in swizzled asid; we get best results with asid in both high & low bits. + * + * page = tlbvirt >> (PGSHIFT+1); // ignoring even/odd bit + * R27 = ((tlbvirt<<(STLBLOG-8) ^ (uchar)tlbvirt ^ page ^ + * ((page & (MASK(HIPFNBITS) << STLBLOG)) >> HIPFNBITS)) & + * (STLBSIZE-1)) * 12; + */ +#define STLBHASH(arg, tmp, tmp2) \ + MOVW arg, tmp2; \ + SRL $(PGSHIFT+1), arg; /* move low page # bits to low bits */ \ + CONST ((MASK(HIPFNBITS) << STLBLOG), tmp); \ + AND arg, tmp; /* extract high page # bits */ \ + SRL $HIPFNBITS, tmp; /* position them */ \ + XOR tmp, arg; /* include them */ \ + MOVW tmp2, tmp; /* asid in low byte */ \ + SLL $(STLBLOG-8), tmp; /* move asid to high bits */ \ + XOR tmp, arg; /* include asid in high bits too */ \ + AND $0xff, tmp2, tmp; /* asid in low byte */ \ + XOR tmp, arg; /* include asid in low bits */ \ + CONST (STLBSIZE-1, tmp); \ + AND tmp, arg /* chop to fit */ + +TEXT stlbhash(SB), $-4 + STLBHASH(R1, R2, R3) + RET + +TEXT utlbmiss(SB), $-4 + /* + * don't use R28 by using constants that span both word halves, + * it's unsaved so far. avoid R24 (up in kernel) and R25 (m in kernel). + */ + /* update statistics */ + CONST (MACHADDR, R26) /* R26 = m-> */ + MOVW 16(R26), R27 + ADDU $1, R27 + MOVW R27, 16(R26) /* m->tlbfault++ */ + + MOVW R23, M(LLADDR) /* save R23, M(LLADDR) seems no meaning */ + +#ifdef KUTLBSTATS + MOVW M(STATUS), R23 + AND $KUSER, R23 + BEQ R23, kmiss + + MOVW 24(R26), R27 + ADDU $1, R27 + MOVW R27, 24(R26) /* m->utlbfault++ */ + JMP either +kmiss: + MOVW 20(R26), R27 + ADDU $1, R27 + MOVW R27, 20(R26) /* m->ktlbfault++ */ +either: +#endif + + /* compute stlb index */ + EHB + MOVW M(TLBVIRT), R27 /* asid in low byte */ + STLBHASH(R27, R26, R23) + MOVW M(LLADDR), R23 /* restore R23 */ + + /* scale to a byte index (multiply by 12) */ + SLL $1, R27, R26 /* × 2 */ + ADDU R26, R27 /* × 3 */ + SLL $2, R27 /* × 12 */ + + CONST (MACHADDR, R26) /* R26 = m-> */ + MOVW 4(R26), R26 /* R26 = m->stb */ + ADDU R26, R27 /* R27 = &m->stb[hash] */ + + MOVW M(BADVADDR), R26 + AND $BY2PG, R26 + BNE R26, utlbodd /* odd page? */ + +utlbeven: + MOVW 4(R27), R26 /* R26 = m->stb[hash].phys0 */ + BEQ R26, stlbm /* nothing cached? do it the hard way */ + MOVW R26, M(TLBPHYS0) + EHB + MOVW 8(R27), R26 /* R26 = m->stb[hash].phys1 */ + MOVW R26, M(TLBPHYS1) + EHB + JMP utlbcom + +utlbodd: + MOVW 8(R27), R26 /* R26 = m->stb[hash].phys1 */ + BEQ R26, stlbm /* nothing cached? do it the hard way */ + MOVW R26, M(TLBPHYS1) + EHB + MOVW 4(R27), R26 /* R26 = m->stb[hash].phys0 */ + MOVW R26, M(TLBPHYS0) + EHB + +utlbcom: + MOVW M(TLBVIRT), R26 + MOVW (R27), R27 /* R27 = m->stb[hash].virt */ + BEQ R27, stlbm /* nothing cached? do it the hard way */ + + /* is the stlb entry for the right virtual address? */ + BNE R26, R27, stlbm /* M(TLBVIRT) != m->stb[hash].virt? */ + + /* if an entry exists, overwrite it, else write a random one */ + CONST (PGSZ, R27) + MOVW R27, M(PAGEMASK) /* select page size */ + EHB + TLBP /* probe tlb */ + EHB + MOVW M(INDEX), R26 + EHB + BGEZ R26, utlindex /* if tlb entry found, rewrite it */ + TLBWR /* else write random tlb entry */ + ERET +utlindex: + TLBWI /* write indexed tlb entry */ + ERET + +/* not in the stlb either; make trap.c figure it out */ +stlbm: + MOVW $exception(SB), R26 + JMP (R26) + +/* vector at KSEG1+0x100, cache error */ +TEXT vector100(SB), $-4 + MOVW $exception(SB), R26 + JMP (R26) + +/* vector at KSEG0+0x180, others */ +TEXT vector180(SB), $-4 + MOVW $exception(SB), R26 + JMP (R26) + +TEXT exception(SB), $-4 + MOVW M(STATUS), R26 + AND $KUSER, R26, R27 + BEQ R27, waskernel + +wasuser: + MOVW R29, R27 + CONST (MACHADDR, R29) /* m-> */ + MOVW 8(R29), R29 /* m->proc */ + MOVW 8(R29), R29 /* m->proc->kstack */ + MOVW M(STATUS), R26 /* redundant load */ + ADDU $(KSTACK-UREGSIZE), R29 + MOVW R31, Ureg_r31(R29) + + JAL savereg1(SB) + + MOVW R30, Ureg_r30(R29) + MOVW R(MACH), Ureg_r25(R29) + MOVW R(USER), Ureg_r24(R29) + + MOVW $setR30(SB), R30 + CONST (MACHADDR, R(MACH)) /* R(MACH) = m-> */ + MOVW 8(R(MACH)), R(USER) /* up = m->proc */ + + AND $(EXCMASK<<2), R26, R1 /* R26 = M(CAUSE) from savereg1 */ + SUBU $(CSYS<<2), R1 + BNE R1, notsys + + MOVW R29, R1 /* first arg for syscall */ + SUBU $Notuoffset, R29 + JAL syscall(SB) + +sysrestore: + ADDU $Notuoffset, R29 + JAL restreg1(SB) + + MOVW Ureg_r31(R29), R31 + MOVW Ureg_status(R29), R26 + MOVW Ureg_r30(R29), R30 + MOVW R26, M(STATUS) + EHB + MOVW Ureg_pc(R29), R26 /* old pc */ + MOVW Ureg_sp(R29), R29 + MOVW R26, M(EPC) + ERET + +notsys: + JAL savereg2(SB) + + MOVW R29, R1 /* first arg for trap */ + SUBU $Notuoffset, R29 + JAL trap(SB) + + ADDU $Notuoffset, R29 + +restore: + JAL restreg1(SB) + JAL restreg2(SB) /* restores R28, among others, R26 = old pc */ + + MOVW Ureg_r30(R29), R30 + MOVW Ureg_r31(R29), R31 + MOVW Ureg_r25(R29), R(MACH) + MOVW Ureg_r24(R29), R(USER) + MOVW Ureg_sp(R29), R29 + MOVW R26, M(EPC) + ERET + +waskernel: + MOVW R29, R27 + SUBU $UREGSIZE, R29 + OR $7, R29 /* conservative rounding */ + XOR $7, R29 + MOVW R31, Ureg_r31(R29) + + JAL savereg1(SB) + JAL savereg2(SB) + + MOVW R29, R1 /* first arg for trap */ + SUBU $Notuoffset, R29 + JAL trap(SB) + + ADDU $Notuoffset, R29 + + JAL restreg1(SB) + JAL restreg2(SB) /* restores R28, among others, R26 = old pc */ + + MOVW Ureg_r31(R29), R31 + MOVW Ureg_sp(R29), R29 + MOVW R26, M(EPC) + ERET + +TEXT forkret(SB), $0 + MOVW R0, R1 + JMP sysrestore + +/* + * save mandatory registers. + * called with old M(STATUS) in R26. + * called with old SP in R27 + * returns with M(CAUSE) in R26 + */ +TEXT savereg1(SB), $-4 + MOVW R1, Ureg_r1(R29) + + MOVW $(~KMODEMASK), R1 /* don't use R28, it's unsaved so far */ + AND R26, R1 + MOVW R1, M(STATUS) /* kernel mode, no interrupt */ + EHB + + MOVW R26, Ureg_status(R29) /* status */ + MOVW R27, Ureg_sp(R29) /* user SP */ + + MOVW M(EPC), R1 + MOVW M(CAUSE), R26 + + MOVW R23, Ureg_r23(R29) + MOVW R22, Ureg_r22(R29) + MOVW R21, Ureg_r21(R29) + MOVW R20, Ureg_r20(R29) + MOVW R19, Ureg_r19(R29) + MOVW R1, Ureg_pc(R29) + RET + +/* + * all other registers. + * called with M(CAUSE) in R26 + */ +TEXT savereg2(SB), $-4 + MOVW R2, Ureg_r2(R29) + + MOVW M(BADVADDR), R2 + MOVW R26, Ureg_cause(R29) + MOVW M(TLBVIRT), R1 + MOVW R2, Ureg_badvaddr(R29) + MOVW R1, Ureg_tlbvirt(R29) + MOVW HI, R1 + MOVW LO, R2 + MOVW R1, Ureg_hi(R29) + MOVW R2, Ureg_lo(R29) + /* LINK,SB,SP missing */ + MOVW R28, Ureg_r28(R29) + /* R27, R26 not saved */ + /* R25, R24 missing */ + /* R23- R19 saved in save1 */ + MOVW R18, Ureg_r18(R29) + MOVW R17, Ureg_r17(R29) + MOVW R16, Ureg_r16(R29) + MOVW R15, Ureg_r15(R29) + MOVW R14, Ureg_r14(R29) + MOVW R13, Ureg_r13(R29) + MOVW R12, Ureg_r12(R29) + MOVW R11, Ureg_r11(R29) + MOVW R10, Ureg_r10(R29) + MOVW R9, Ureg_r9(R29) + MOVW R8, Ureg_r8(R29) + MOVW R7, Ureg_r7(R29) + MOVW R6, Ureg_r6(R29) + MOVW R5, Ureg_r5(R29) + MOVW R4, Ureg_r4(R29) + MOVW R3, Ureg_r3(R29) + RET + +/* restore R23-R19 */ +TEXT restreg1(SB), $-4 + MOVW Ureg_r23(R29), R23 + MOVW Ureg_r22(R29), R22 + MOVW Ureg_r21(R29), R21 + MOVW Ureg_r20(R29), R20 + MOVW Ureg_r19(R29), R19 + RET + +/* + * all other registers. + * returns with pc in R26 + */ +TEXT restreg2(SB), $-4 + /* LINK,SB,SP missing */ + MOVW Ureg_r28(R29), R28 + /* R27, R26 not saved */ + /* R25, R24 missing */ + /* R19- R23 restored in rest1 */ + MOVW Ureg_r18(R29), R18 + MOVW Ureg_r17(R29), R17 + MOVW Ureg_r16(R29), R16 + MOVW Ureg_r15(R29), R15 + MOVW Ureg_r14(R29), R14 + MOVW Ureg_r13(R29), R13 + MOVW Ureg_r12(R29), R12 + MOVW Ureg_r11(R29), R11 + MOVW Ureg_r10(R29), R10 + MOVW Ureg_r9(R29), R9 + MOVW Ureg_r8(R29), R8 + MOVW Ureg_r7(R29), R7 + MOVW Ureg_r6(R29), R6 + MOVW Ureg_r5(R29), R5 + MOVW Ureg_r4(R29), R4 + MOVW Ureg_r3(R29), R3 + MOVW Ureg_lo(R29), R2 + MOVW Ureg_hi(R29), R1 + MOVW R2, LO + MOVW R1, HI + + MOVW Ureg_status(R29), R1 + MOVW Ureg_r2(R29), R2 + MOVW R1, M(STATUS) /* could change interruptibility */ + EHB + MOVW Ureg_r1(R29), R1 /* BOTCH */ + MOVW Ureg_pc(R29), R26 + RET + +/* + * floating point stuff + */ + +TEXT savefpregs(SB), $0 + MOVW FCR31, R2 + MOVW M(STATUS), R3 + AND $~FPEXCMASK, R2, R4 + MOVW R4, FCR31 + + MOVD F0, (0*8)(R1) + MOVD F2, (1*8)(R1) + MOVD F4, (2*8)(R1) + MOVD F6, (3*8)(R1) + MOVD F8, (4*8)(R1) + MOVD F10, (5*8)(R1) + MOVD F12, (6*8)(R1) + MOVD F14, (7*8)(R1) + MOVD F16, (8*8)(R1) + MOVD F18, (9*8)(R1) + MOVD F20, (10*8)(R1) + MOVD F22, (11*8)(R1) + MOVD F24, (12*8)(R1) + MOVD F26, (13*8)(R1) + MOVD F28, (14*8)(R1) + MOVD F30, (15*8)(R1) + + MOVW R2, (16*8)(R1) /* FCR31 */ + AND $~CU1, R3 + MOVW R3, M(STATUS) + EHB + RET + +TEXT restfpregs(SB), $0 /* restfpregs(fpsave, fpstatus) */ + MOVW M(STATUS), R3 + OR $CU1, R3 + MOVW R3, M(STATUS) + EHB + MOVW 4(FP), R2 + MOVW R2, FCR31 + NOP + + MOVD (0*8)(R1), F0 + MOVD (1*8)(R1), F2 + MOVD (2*8)(R1), F4 + MOVD (3*8)(R1), F6 + MOVD (4*8)(R1), F8 + MOVD (5*8)(R1), F10 + MOVD (6*8)(R1), F12 + MOVD (7*8)(R1), F14 + MOVD (8*8)(R1), F16 + MOVD (9*8)(R1), F18 + MOVD (10*8)(R1), F20 + MOVD (11*8)(R1), F22 + MOVD (12*8)(R1), F24 + MOVD (13*8)(R1), F26 + MOVD (14*8)(R1), F28 + MOVD (15*8)(R1), F30 + + AND $~CU1, R3 + MOVW R3, M(STATUS) + EHB + RET + +TEXT fcr31(SB), $0 + MOVW FCR31, R1 + MOVW M(STATUS), R3 + AND $~CU1, R3 + MOVW R3, M(STATUS) + EHB + RET + +TEXT clrfpintr(SB), $0 + MOVW M(STATUS), R3 + OR $CU1, R3 + MOVW R3, M(STATUS) + EHB + + MOVW FCR31, R1 + AND $~FPEXCMASK, R1, R2 + MOVW R2, FCR31 + + AND $~CU1, R3 + MOVW R3, M(STATUS) + EHB + RET + +/* + * Emulate 68020 test and set: load linked / store conditional + */ + +TEXT tas(SB), $0 + MOVW R1, R2 /* address of key */ +tas1: + MOVW $1, R3 + LL(2, 1) + NOP + SC(2, 3) + NOP + BEQ R3, tas1 + RET + +TEXT _xinc(SB), $0 + MOVW R1, R2 /* address of counter */ +loop: + MOVW $1, R3 + LL(2, 1) + NOP + ADDU R1, R3 + MOVW R3, R1 /* return new value */ + SC(2, 3) + NOP + BEQ R3, loop + RET + +TEXT _xdec(SB), $0 + SYNC + NOP + NOP + MOVW R1, R2 /* address of counter */ +loop1: + MOVW $-1, R3 + LL(2, 1) + NOP + ADDU R1, R3 + MOVW R3, R1 /* return new value */ + SC(2, 3) + NOP + BEQ R3, loop1 + RET + +TEXT cmpswap(SB), $0 + MOVW R1, R2 /* address of key */ + MOVW old+4(FP), R3 /* old value */ + MOVW new+8(FP), R4 /* new value */ + LL(2, 1) /* R1 = (R2) */ + NOP + BNE R1, R3, fail + MOVW R4, R1 + SC(2, 1) /* (R2) = R1 if (R2) hasn't changed; R1 = success */ + NOP + RET +fail: + MOVW R0, R1 + RET + +/* + * cache manipulation + */ + +/* + * we avoided using R4, R5, R6, and R7 so gotopc can call us without saving + * them, but gotopc is now gone. + */ +TEXT icflush(SB), $-4 /* icflush(virtaddr, count) */ + MOVW M(STATUS), R10 /* old status -> R10 */ + MOVW 4(FP), R9 + MOVW R0, M(STATUS) /* intrs off */ + EHB + + TOKSEG1(11) /* return to kseg1 (uncached) */ + ADDU R1, R9 /* R9 = last address */ + MOVW $(~(CACHELINESZ-1)), R8 + AND R1, R8 /* R8 = first address, rounded down */ + ADDU $(CACHELINESZ-1), R9 + AND $(~(CACHELINESZ-1)), R9 /* round last address up */ + SUBU R8, R9 /* R9 = revised count */ +icflush1: +// CACHE PD+HWB, (R8) /* flush D to ram */ +// CACHE PI+HINV, (R8) /* invalidate in I */ + CACHE SD+HWBI, (R8) /* flush & invalidate thru L2 */ + SUBU $CACHELINESZ, R9 + ADDU $CACHELINESZ, R8 + BGTZ R9, icflush1 + + TOKSEG0(11) /* return to kseg0 (cached) */ + MOVW R10, M(STATUS) + EHB + RET + +TEXT dcflush(SB), $-4 /* dcflush(virtaddr, count) */ + MOVW M(STATUS), R10 /* old status -> R10 */ + MOVW 4(FP), R9 + MOVW R0, M(STATUS) /* intrs off */ + EHB + SYNC + + TOKSEG1(11) /* return to kseg1 (uncached) */ + ADDU R1, R9 /* R9 = last address */ + MOVW $(~(CACHELINESZ-1)), R8 + AND R1, R8 /* R8 = first address, rounded down */ + ADDU $(CACHELINESZ-1), R9 + AND $(~(CACHELINESZ-1)), R9 /* round last address up */ + SUBU R8, R9 /* R9 = revised count */ +dcflush1: +// CACHE PI+HINV, (R8) /* invalidate in I */ +// CACHE PD+HWBI, (R8) /* flush & invalidate in D */ + CACHE SD+HWBI, (R8) /* flush & invalidate thru L2 */ + SUBU $CACHELINESZ, R9 + ADDU $CACHELINESZ, R8 + BGTZ R9, dcflush1 + SYNC + + TOKSEG0(11) /* return to kseg0 (cached) */ + MOVW R10, M(STATUS) + EHB + RET + +TEXT dcinvalid(SB), $-4 /* dcinvalid(virtaddr, count) */ + MOVW M(STATUS), R10 /* old status -> R10 */ + MOVW 4(FP), R9 + MOVW R0, M(STATUS) /* intrs off */ + EHB + SYNC + + TOKSEG1(11) /* return to kseg1 (uncached) */ + ADDU R1, R9 /* R9 = last address */ + MOVW $(~(CACHELINESZ-1)), R8 + AND R1, R8 /* R8 = first address, rounded down */ + ADDU $(CACHELINESZ-1), R9 + AND $(~(CACHELINESZ-1)), R9 /* round last address up */ + SUBU R8, R9 /* R9 = revised count */ +dcinvalid1: +// CACHE PD+HINV, (R8) /* invalidate in D */ + CACHE SD+HINV, (R8) /* invalidate thru L2 */ + SUBU $CACHELINESZ, R9 + ADDU $CACHELINESZ, R8 + BGTZ R9, dcinvalid1 + SYNC + + TOKSEG0(11) /* return to kseg0 (cached) */ + MOVW R10, M(STATUS) + EHB + RET + +TEXT cleancache(SB), $-4 + MOVW M(STATUS), R10 /* old status -> R10 */ + MOVW R0, M(STATUS) /* intrs off */ + EHB + + TOKSEG1(11) /* return to kseg1 (uncached) */ + MOVW $KSEG0, R1 /* index, not address, kseg0 avoids tlb */ + MOVW $(SCACHESIZE/4), R9 /* 4-way cache */ +ccache: + CACHE SD+IWBI, 0(R1) /* flush & invalidate thru L2 by index */ + CACHE SD+IWBI, 1(R1) /* ways are least significant bits */ + CACHE SD+IWBI, 2(R1) + CACHE SD+IWBI, 3(R1) + SUBU $CACHELINESZ, R9 + ADDU $CACHELINESZ, R1 + BGTZ R9, ccache + SYNC + + TOKSEG0(11) /* return to kseg0 (cached) */ + MOVW R10, M(STATUS) + EHB + RET + +/* + * PMON routines, for early debugging. + * wrapper converts PMON's calling convention to 0c's. + * we need to leave CFSZ space to prevent stack corrupted + */ + +#define CFSZ 48 // XXX + +DATA pmon_callvec(SB)/4, $0 /* pmon call vector */ +GLOBL pmon_callvec(SB), $4 + +#define pmon_wrap(name, index) \ +TEXT name(SB), $((CFSZ+12+7)&(~7)); \ + MOVW R1, R4; \ + MOVW arg2+4(FP), R5; \ + MOVW arg3+8(FP), R6; \ + MOVW arg4+12(FP), R7; \ + MOVW R29, R2; \ + AND $~7, R29; /* pmon needs R29 8-aligned */ \ + MOVW R31, -4(SP); \ + MOVW R30, -8(SP); \ + MOVW R2, -12(SP); \ + MOVW pmon_callvec(SB), R8; \ + MOVW (4*(index))(R8), R8; \ + JAL (R8); \ + MOVW -8(SP), R30; \ + MOVW -4(SP), R31; \ + MOVW -12(SP), R29; \ + MOVW R2, R1; \ + MOVW R31, 0(R29); \ + RET + +pmon_wrap(pmonprint, 5) --- /dev/null +++ /sys/src/9/loongson/lnbzroot @@ -0,0 +1,68 @@ +dev + root + cons + arch + cap + dup + env + fs + kprof + mnt + pipe + pnp pci + proc + rtc + sd + srv + ssl + tls + uart + + ether netif + ip arp chandial inferno ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum + + draw screen + kbin kbd latin1 + kbmap + mouse mouse + + usb + +link + loopbackmedium + ethermedium + ether8139 pci +# usbohci pci + +misc + uarti8250 + sdata pci sdscsi + +ip + tcp + udp + ipifc + icmp + icmp6 + gre + ipmux + esp + +port + int cpuserver = 0; + int screenwid = 640; + int screenht = 480; + int screendepth = 16; + ulong cpufreq = 667*Mhz; + +boot boot + local + tcp + +bootdir + boot$CONF.out boot + /sys/lib/dist/bin/spim/bzfs kfs + /sys/lib/dist/loongson/root.bz2 bzroot + /spim/bin/ip/ipconfig + /spim/bin/auth/factotum + /spim/bin/usb/usbd --- /dev/null +++ /sys/src/9/loongson/lncpu @@ -0,0 +1,66 @@ +dev + root + cons + arch + cap + dup + env + fs + kprof + mnt + pipe + pnp pci + proc + rtc + sd + srv + ssl + tls + uart + + ether netif + ip arp chandial inferno ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum + + draw screen + kbin kbd latin1 + kbmap + mouse mouse + + usb + +link + loopbackmedium + ethermedium + ether8139 pci +# usbohci pci + +misc + uarti8250 + sdata pci sdscsi + +ip + tcp + udp + ipifc + icmp + icmp6 + gre + ipmux + esp + +port + int cpuserver = 1; + int screenwid = 640; + int screenht = 480; + int screendepth = 16; + ulong cpufreq = 667*Mhz; + +boot cpu + tcp + +bootdir + boot$CONF.out boot + /spim/bin/ip/ipconfig + /spim/bin/auth/factotum + /spim/bin/usb/usbd + nvram --- /dev/null +++ /sys/src/9/loongson/main.c @@ -0,0 +1,755 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../ip/ip.h" +#include +#include +#include +#include "init.h" +#include "reboot.h" + +enum { + /* space for syscall args, return PC, top-of-stack struct */ + Stkheadroom = sizeof(Sargs) + sizeof(uintptr) + sizeof(Tos), +}; + +typedef struct mipsexec Mipsexec; + +#define MAXCONF 64 +#define CONFBUFLEN 4096 +#define MAXBOOTENV 256 + +/* only for reboot */ +#define BOOTARGS ((char*)(CONFADDR)) +#define BOOTARGSLEN CONFBUFLEN +#define CONFARGVLEN (MAXCONF*BY2WD) + +/* args passed by boot loader for main() */ +int _argc; +char **_argv; +char **_env; +static char *bootenvname[MAXBOOTENV]; +static char *bootenvval[MAXBOOTENV]; +static int nenv; + +/* plan9.ini */ +static char confbuf[CONFBUFLEN]; +static char *confname[MAXCONF]; +static char *confval[MAXCONF]; +static int nconf; + +/* + * Option arguments from the command line. + * oargv[0] is the boot file. + */ +static int oargc; +static char* oargv[20]; +static char oargb[128]; +static int oargblen; + +static uintptr sp; /* XXX - must go - user stack of init proc */ + +/* + * software tlb simulation + */ +static Softtlb stlb[MAXMACH][STLBSIZE]; + +Mach *machaddr[MAXMACH]; +Conf conf; +ulong memsize; +FPsave initfp; + +int normalprint; +ulong cpufreq; + +/* + * initialize a processor's mach structure. each processor does this + * for itself. + */ +void +machinit(void) +{ + /* Ensure CU1 is off */ + clrfpintr(); + + m->stb = &stlb[m->machno][0]; + + clockinit(); +} + +static void +optionsinit(char* s) +{ + strecpy(oargb, oargb+sizeof(oargb), s); + + oargblen = strlen(oargb); + oargc = tokenize(oargb, oargv, nelem(oargv)-1); + oargv[oargc] = nil; +} + +void +plan9iniinit(void) +{ + char *cp; + int i; + + nconf = 0; + if(_argv != nil){ + memmove(confbuf, _argv[0], sizeof(confbuf)); + for(i=1; i<_argc && nconf= 0) + return confval[i]; + return nil; +} + +static int +findbootenv(char *name) +{ + int i; + + for(i = 0; i < nenv; i++) + if(cistrcmp(bootenvname[i], name) == 0) + return i; + return -1; +} + +static char* +getbootenv(char *name) +{ + int i; + + i = findbootenv(name); + if(i >= 0) + return bootenvval[i]; + return nil; +} + +void +confinit(void) +{ + char *p; + int i; + ulong kpages, ktop; + ulong maxmem, highmemsize; + + /* memory size */ + memsize = MEMSIZE; + if((p = getbootenv("memsize")) != nil) + memsize = strtoul(p, 0, 0) * MB; + highmemsize = 0; + if((p = getbootenv("highmemsize")) != nil) + highmemsize = strtoul(p, 0, 0) * MB; + if((p = getconf("*maxmem")) != nil){ + maxmem = strtoul(p, 0, 0); + memsize = MIN(memsize, maxmem); + maxmem -= memsize; + highmemsize = MIN(highmemsize, maxmem); + if(memsize < 16*MB) /* sanity */ + memsize = 16*MB; + } + + /* set up other configuration parameters */ + conf.nproc = 2000; + conf.nswap = 262144; + conf.nswppo = 4096; + conf.nimage = 200; + + /* + * divide memory to user pages and kernel. + */ + conf.mem[0].base = ktop = PADDR(PGROUND((ulong)end)); + conf.mem[0].npage = memsize/BY2PG - ktop/BY2PG; + + conf.mem[1].base = HIGHMEM; + conf.mem[1].npage = highmemsize/BY2PG; + + conf.npage = 0; + for(i=0; i (64*MB + conf.npage*sizeof(Page))/BY2PG){ + kpages = (64*MB + conf.npage*sizeof(Page))/BY2PG; + kpages += (conf.nproc*KSTACK)/BY2PG; + } + conf.upages = conf.npage - kpages; + conf.ialloc = (kpages/2)*BY2PG; + + kpages *= BY2PG; + kpages -= conf.upages*sizeof(Page) + + conf.nproc*sizeof(Proc) + + conf.nimage*sizeof(Image) + + conf.nswap + + conf.nswppo*sizeof(Page); + mainmem->maxsize = kpages; + + /* + * set up CPU's mach structure + * cpu0's was zeroed in main() and our stack is in Mach, so don't zero it. + */ + m->machno = 0; + m->hz = cpufreq; /* initial guess at MHz */ + m->speed = m->hz / Mhz; + conf.nmach = 1; + + conf.nuart = 1; + conf.copymode = 0; /* copy on write */ +} + +void +procrestore(Proc *p) +{ + uvlong t; + + if(p->kp) + return; + cycles(&t); + p->pcycles -= t; +} + +/* + * Save the mach dependent part of the process state. + */ +void +procsave(Proc *p) +{ + uvlong t; + + cycles(&t); + p->pcycles += t; + //fpuprocsave(); // XXX +} + +static void +fmtinit(void) +{ + printinit(); + quotefmtinstall(); + /* ipreset installs these when chandevreset runs */ + fmtinstall('i', eipfmt); + fmtinstall('I', eipfmt); + fmtinstall('E', eipfmt); + fmtinstall('V', eipfmt); + fmtinstall('M', eipfmt); +} + +/* + * setup MIPS trap vectors + */ +void +vecinit(void) +{ + memmove((ulong*)UTLBMISS, (ulong*)vector0, 0x80); + memmove((ulong*)XEXCEPTION, (ulong*)vector0, 0x80); + memmove((ulong*)CACHETRAP, (ulong*)vector100, 0x80); + memmove((ulong*)EXCEPTION, (ulong*)vector180, 0x80); + /* 0x200 not used in looongson 2e */ + icflush((ulong*)UTLBMISS, 4*1024); + + setstatus(getstatus() & ~BEV); +} + +static void +prcpuid(void) +{ + ulong cpuid, cfg; + char *cpu; + + cpuid = prid(); + cfg = getconfig(); + if (((cpuid>>16) & MASK(8)) == 0) /* vendor */ + cpu = "old mips"; + else if (((cpuid>>16) & MASK(8)) == 1) + switch ((cpuid>>8) & MASK(8)) { /* processor */ + case 0x93: + cpu = "mips 24k"; + break; + case 0x96: + cpu = "mips 24ke"; + break; + default: + cpu = "mips"; + break; + } + else + cpu = "other mips"; + delay(200); + print("cpu%d: %ldMHz %s %se 0x%ulx v%ld.%ld rev %ld has fpu\n", + m->machno, m->hz / Mhz, cpu, cfg & (1<<15)? "b": "l", + (cpuid>>8) & MASK(8), (cpuid>>5) & MASK(3), (cpuid>>2) & MASK(3), + cpuid & MASK(2)); + delay(200); + + print("cpu%d: %d tlb entries, using %dK pages\n", m->machno, + 64, BY2PG/1024); + delay(50); + print("cpu%d: l1 i cache: %d sets 4 ways 32 bytes/line\n", m->machno, + 32 << ((cfg>>9) & MASK(3))); + delay(50); + print("cpu%d: l1 d cache: %d sets 4 ways 32 bytes/line\n", m->machno, + 32 << ((cfg>>6) & MASK(3))); + delay(500); +} + +static int +ckpagemask(ulong mask, ulong size) +{ + int s; + ulong pm; + + s = splhi(); + setpagemask(mask); + pm = getpagemask(); + splx(s); + if(pm != mask){ + iprint("page size %ldK not supported on this cpu; " + "mask %#lux read back as %#lux\n", size/1024, mask, pm); + return -1; + } + return 0; +} + +void +init0(void) +{ + char buf[128]; + int i; + + up->nerrlab = 0; + + 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()){ + ksetenv("cputype", "spim", 0); + snprint(buf, sizeof buf, "spim %s loongson 2e", conffile); + ksetenv("terminal", buf, 0); + if(cpuserver) + ksetenv("service", "cpu", 0); + else + ksetenv("service", "terminal", 0); +// ksetenv("nobootprompt", "local!/boot/bzroot", 0); // for bzroot + ksetenv("nvram", "/boot/nvram", 0); + /* convert plan9.ini variables to #e and #ec */ + for(i = 0; i < nconf; i++) { + if(*confname[i] == '*') + continue; + ksetenv(confname[i], confval[i], 0); + ksetenv(confname[i], confval[i], 1); + } + poperror(); + } + kproc("alarm", alarmkproc, 0); + i8250console(); + 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 - Stkheadroom - i)); + memmove(p, oargb, i); + + /* + * Now push the argv pointers. + * The code jumped to by touser in l.s expects arguments + * main(char* argv0, ...) + * and calls + * startboot("/boot/boot", &argv0) + * not the usual (int argc, char* argv[]) + */ + av = (char**)(p - (oargc+1)*sizeof(char*)); + ssize = base + BY2PG - PTR2UINT(av); + for(i = 0; i < oargc; i++) + *av++ = (oargv[i] - oargb) + (p - base) + (USTKTOP - BY2PG); + *av = nil; + sp = USTKTOP - ssize; +} + +void +userinit(void) +{ + Proc *p; + KMap *k; + Page *pg; + Segment *s; + + 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); + + p->fpstate = FPinit; + p->fpsave.fpstatus = initfp.fpstatus; + + /* + * Kernel Stack + */ + p->sched.pc = (ulong)init0; + p->sched.sp = (ulong)p->kstack+KSTACK-Stkheadroom; + 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); + memset(pg->cachectl, PG_DATFLUSH, sizeof(pg->cachectl)); + 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]); + memset((void*)VA(k), 0, BY2PG); + memmove((ulong*)VA(k), initcode, sizeof initcode); + kunmap(k); + + ready(p); +} + +/* called from rebootcmd() */ +int +parsemipsboothdr(Chan *c, ulong magic, Execvals *evp) +{ + long extra; + Mipsexec me; + + /* + * BOOT_MAGIC is sometimes defined like this: + * #define BOOT_MAGIC (0x160<<16) || magic == ((0x160<<16)|3) + * so we can only use it in a fairly stylized manner. + */ + if(magic == BOOT_MAGIC) { + c->offset = 0; /* back up */ + readn(c, &me, sizeof me); + /* if binary is -H1, read an extra long */ + if (l2be(me.amagic) == 0407 && me.nscns == 0) + readn(c, &extra, sizeof extra); + evp->entry = l2be(me.mentry); + evp->textsize = l2be(me.tsize); + evp->datasize = l2be(me.dsize); + return 0; + } else + return -1; +} + +void +main(void) +{ + extern char edata[], end[]; + + m = (Mach*)MACHADDR; + memset(m, 0, sizeof(Mach)); /* clear Mach */ + m->machno = 0; + machaddr[m->machno] = m; + up = nil; + memset(edata, 0, end-edata); /* clear bss */ + + optionsinit("/boot/boot boot"); + plan9iniinit(); + confinit(); + savefpregs(&initfp); + + machinit(); + active.exiting = 0; + active.machs = 1; + + kmapinit(); + xinit(); + screeninit(); + + ckpagemask(PGSZ, BY2PG); + tlbinit(); + machwire(); + + timersinit(); + fmtinit(); + vecinit(); + + normalprint = 1; + print("\nPlan 9\n"); + print("\0\0\0\0\0\0\0"); // XXX 0c bug fixed -- leave this for sanity + prcpuid(); + if(PTECACHABILITY == PTENONCOHERWT) + print("caches configured as write-through\n"); +// xsummary(); + + i8259init(); + kbdinit(); + if(conf.monitor) + swcursorinit(); + + pageinit(); + procinit0(); + initseg(); + links(); + chandevreset(); + + swapinit(); + userinit(); + parseboothdr = parsemipsboothdr; + schedinit(); + panic("schedinit returned"); +} + +static void +writeconf(void) +{ + char *p, *q; + int n; + + p = getconfenv(); + + if(waserror()) { + free(p); + nexterror(); + } + + /* convert to name=value\0 format */ + _argc = 1; /* skip _argv[0] */ + _argv = (void*)CONFARGV; + _argv[0] = BOOTARGS; + for(q=p; *q; q++) { + _argv[_argc] = (char*)((ulong)BOOTARGS + (q - p)); + _argc++; + q += strlen(q); + *q = '='; + q += strlen(q); + } + n = q - p + 1; + _env = &_argv[_argc]; + + if(n >= BOOTARGSLEN || (ulong)_env >= CONFARGV + CONFARGVLEN) + error("kernel configuration too large"); + memmove(BOOTARGS, p, n); + memset(BOOTARGS + n, '\0', BOOTARGSLEN - n); + memset(_env, 0, CONFARGV + CONFARGVLEN - (ulong)_env); + + poperror(); + free(p); +} + +static void +shutdown(int ispanic) +{ + int ms, once; + + ilock(&active); + if(ispanic) + active.ispanic = ispanic; + else if(m->machno == 0 && (active.machs & (1<machno)) == 0) + active.ispanic = 0; + once = active.machs & (1<machno); + /* + * setting exiting will make hzclock() on each processor call exit(0), + * which calls shutdown(0) and idles non-bootstrap cpus and returns + * on bootstrap processors (to permit a reboot). clearing our bit + * in machs avoids calling exit(0) from hzclock() on this processor. + */ + active.machs &= ~(1<machno); + active.exiting = 1; + iunlock(&active); + + if(once) { + delay(m->machno*1000); /* stagger them */ + iprint("cpu%d: exiting\n", m->machno); + } + spllo(); + for(ms = MAXMACH * 1000; ms > 0; ms -= TK2MS(2)){ + delay(TK2MS(2)); + if(active.machs == 0 && consactive() == 0) + break; + } + delay(100); +} + +void +exit(int ispanic) +{ + int timer; + + delay(1000); + shutdown(ispanic); + timer = 0; + while(active.machs || consactive()) { + if(timer++ > 400) + break; + delay(10); + } + delay(1000); + splhi(); + + setstatus(BEV); + coherence(); + + iprint("exit: awaiting reset\n"); + delay(1000); /* await a reset */ + + if(!active.ispanic) { + iprint("exit: jumping to rom\n"); + *Reset |= Rstcpucold; /* cpu cold reset */ + } + + iprint("exit: looping\n"); + for(;;); +} + +/* + * 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, ulong); + + writeconf(); + + /* + * the boot processor is cpu0. execute this function on it + * so that the new kernel has the same cpu0. + */ + if(m->machno != 0) { + procwired(up, 0); + sched(); + } + if(m->machno != 0) + print("on cpu%d (not 0)!\n", m->machno); + + if(code == nil || entry == nil) + exit(1); + + shutdown(0); + + /* + * should be the only processor running now + */ + iprint("reboot: entry %#p code %#p size %ld\n", entry, code, size); + iprint("code[0] = %#lux\n", *(ulong *)code); + + /* turn off buffered serial console */ + serialoq = nil; + kprintoq = nil; + screenputs = nil; + + /* shutdown devices */ + chandevshutdown(); + + splhi(); + intrshutdown(); + + /* setup reboot trampoline function */ + f = (void*)REBOOTADDR; + memmove(f, rebootcode, sizeof(rebootcode)); + icflush(f, sizeof(rebootcode)); + + setstatus(BEV); /* also, kernel mode, no interrupts */ + coherence(); + + /* off we go - never to return */ + (*f)((ulong)entry, (ulong)code, size, _argc); + + panic("loaded kernel returned!"); +} + +/* + * stub for ../port/devpnp.c + */ +int +isaconfig(char *class, int ctlrno, ISAConf *isa) +{ + USED(class, ctlrno, isa); + return 0; +} --- /dev/null +++ /sys/src/9/loongson/mem.h @@ -0,0 +1,346 @@ +/* + * Memory and machine-specific definitions. Used in C and assembler. + */ + +/* + * Sizes + */ + +#define BI2BY 8 /* bits per byte */ +#define BI2WD 32 /* bits per word */ +#define BY2WD 4 /* bytes per word */ +#define BY2V 8 /* bytes per vlong */ + +#define MAXBY2PG (16*1024) /* rounding for UTZERO in executables; see mkfile */ +#define UTROUND(t) ROUNDUP((t), MAXBY2PG) + +#define BIGPAGES /* use 16K page */ +#ifndef BIGPAGES +#define BY2PG 4096 /* bytes per page */ +#define PGSHIFT 12 /* log2(BY2PG) */ +#define PGSZ PGSZ4K +#define MACHSIZE (2*BY2PG) +#else +#define BY2PG (16*1024) /* bytes per page */ +#define PGSHIFT 14 /* log2(BY2PG) */ +#define PGSZ PGSZ16K +#define MACHSIZE BY2PG +#endif + +#define KSTACK 8192 /* Size of kernel stack */ +#define WD2PG (BY2PG/BY2WD) /* words per page */ + +#define MAXMACH 1 /* max # cpus system can run; see active.machs */ +#define STACKALIGN(sp) ((sp) & ~7) /* bug: assure with alloc */ +#define BLOCKALIGN 16 +#define CACHELINESZ 32 /* loongson 2e */ +#define ICACHESIZE (64*1024) /* loongson 2e */ +#define DCACHESIZE (64*1024) /* loongson 2e */ +#define SCACHESIZE (512*1024) /* L2 cache, loongson 2e */ + +#define MASK(w) FMASK(0, w) + +/* + * Time + */ +#define HZ 100 /* clock frequency */ +#define MS2HZ (1000/HZ) /* millisec per clock tick */ +#define TK2SEC(t) ((t)/HZ) /* ticks to seconds */ + +/* + * CP0 registers + */ + +#define INDEX 0 +#define RANDOM 1 +#define TLBPHYS0 2 /* aka ENTRYLO0 */ +#define TLBPHYS1 3 /* aka ENTRYLO1 */ +#define CONTEXT 4 +#define PAGEMASK 5 +#define WIRED 6 +#define BADVADDR 8 +#define COUNT 9 +#define TLBVIRT 10 /* aka ENTRYHI */ +#define COMPARE 11 +#define STATUS 12 +#define CAUSE 13 +#define EPC 14 +#define PRID 15 +#define CONFIG 16 +#define LLADDR 17 +#define WATCHLO 18 +#define WATCHHI 19 +#define XCONTEXT 20 +#define DIAGNOSE 22 /* loongson 2e */ +#define PERFCTL 24 +#define PERFCOUNT 25 +#define CACHEECC 26 +#define CACHEERR 27 +#define TAGLO 28 +#define TAGHI 29 +#define ERROREPC 30 + +/* + * M(STATUS) bits + */ +#define KMODEMASK 0x0000001f +#define IE 0x00000001 /* master interrupt enable */ +#define EXL 0x00000002 /* exception level */ +#define ERL 0x00000004 /* error level */ +#define KSUPER 0x00000008 +#define KUSER 0x00000010 +#define KSU 0x00000018 +#define UX 0x00000020 /* no [USK]X 64-bit extension bits on 24k */ +#define SX 0x00000040 +#define KX 0x00000080 +#define INTMASK 0x0000ff00 +#define INTR0 0x00000100 /* interrupt enable bits */ +#define INTR1 0x00000200 +#define INTR2 0x00000400 +#define INTR3 0x00000800 +#define INTR4 0x00001000 +#define INTR5 0x00002000 +#define INTR6 0x00004000 +#define INTR7 0x00008000 +//#define DE 0x00010000 /* not on 24k */ +#define TS 0x00200000 /* tlb shutdown; on 24k at least */ +#define BEV 0x00400000 /* bootstrap exception vectors */ +#define RE 0x02000000 /* reverse-endian in user mode */ +#define FR 0x04000000 /* enable 32 FP regs */ +#define CU0 0x10000000 +#define CU1 0x20000000 /* FPU enable */ + +/* + * M(CONFIG) bits + */ + +#define CFG_K0 7 /* kseg0 cachability */ + +/* + * M(CAUSE) bits + */ + +#define BD (1<<31) /* last excep'n occurred in branch delay slot */ + +/* + * Exception codes + */ +#define EXCMASK 0x1f /* mask of all causes */ +#define CINT 0 /* external interrupt */ +#define CTLBM 1 /* TLB modification: store to unwritable page */ +#define CTLBL 2 /* TLB miss (load or fetch) */ +#define CTLBS 3 /* TLB miss (store) */ +#define CADREL 4 /* address error (load or fetch) */ +#define CADRES 5 /* address error (store) */ +#define CBUSI 6 /* bus error (fetch) */ +#define CBUSD 7 /* bus error (data load or store) */ +#define CSYS 8 /* system call */ +#define CBRK 9 /* breakpoint */ +#define CRES 10 /* reserved instruction */ +#define CCPU 11 /* coprocessor unusable */ +#define COVF 12 /* arithmetic overflow */ +#define CTRAP 13 /* trap */ +#define CVCEI 14 /* virtual coherence exception (instruction) */ +#define CFPE 15 /* floating point exception */ +#define CTLBRI 19 /* tlb read-inhibit */ +#define CTLBXI 20 /* tlb execute-inhibit */ +#define CWATCH 23 /* watch exception */ +#define CMCHK 24 /* machine checkcore */ +#define CCACHERR 30 /* cache error */ +#define CVCED 31 /* virtual coherence exception (data) */ + +/* + * M(CACHEECC) a.k.a. ErrCtl bits + */ +#define PE (1<<31) +#define LBE (1<<25) +#define WABE (1<<24) + +/* + * FCR31 bits, complement to u.h + */ +#define FPCINEX (1<<12) /* causes */ +#define FPCOVFL (1<<13) +#define FPCUNFL (1<<14) +#define FPCZDIV (1<<15) +#define FPCINVAL (1<<16) +#define FPUNIMP (1<<17) +#define FPEXCMASK (0x3f<<12) + +#define FPFLUSH (1<<24) + +/* + * Trap vectors + */ + +#define UTLBMISS (KSEG0+0x000) +#define XEXCEPTION (KSEG0+0x080) +#define CACHETRAP (KSEG0+0x100) +#define EXCEPTION (KSEG0+0x180) + +/* + * Magic registers + */ + +#define USER 24 /* R24 is up-> */ +#define MACH 25 /* R25 is m-> */ + +/* + * offsets in ureg.h for l.s + */ +#define Ureg_status (Uoffset+0) +#define Ureg_pc (Uoffset+4) +#define Ureg_sp (Uoffset+8) +#define Ureg_cause (Uoffset+12) +#define Ureg_badvaddr (Uoffset+16) +#define Ureg_tlbvirt (Uoffset+20) + +#define Ureg_hi (Uoffset+24) +#define Ureg_lo (Uoffset+28) +#define Ureg_r31 (Uoffset+32) +#define Ureg_r30 (Uoffset+36) +#define Ureg_r28 (Uoffset+40) +#define Ureg_r27 (Uoffset+44) +#define Ureg_r26 (Uoffset+48) +#define Ureg_r25 (Uoffset+52) +#define Ureg_r24 (Uoffset+56) +#define Ureg_r23 (Uoffset+60) +#define Ureg_r22 (Uoffset+64) +#define Ureg_r21 (Uoffset+68) +#define Ureg_r20 (Uoffset+72) +#define Ureg_r19 (Uoffset+76) +#define Ureg_r18 (Uoffset+80) +#define Ureg_r17 (Uoffset+84) +#define Ureg_r16 (Uoffset+88) +#define Ureg_r15 (Uoffset+92) +#define Ureg_r14 (Uoffset+96) +#define Ureg_r13 (Uoffset+100) +#define Ureg_r12 (Uoffset+104) +#define Ureg_r11 (Uoffset+108) +#define Ureg_r10 (Uoffset+112) +#define Ureg_r9 (Uoffset+116) +#define Ureg_r8 (Uoffset+120) +#define Ureg_r7 (Uoffset+124) +#define Ureg_r6 (Uoffset+128) +#define Ureg_r5 (Uoffset+132) +#define Ureg_r4 (Uoffset+136) +#define Ureg_r3 (Uoffset+140) +#define Ureg_r2 (Uoffset+144) +#define Ureg_r1 (Uoffset+148) + +/* ch and carrera used these defs */ + /* Sizeof(Ureg) + (R5,R6) + 16 bytes slop + retpc + ur */ +// #define UREGSIZE ((Ureg_r1+4-Uoffset) + 2*BY2V + 16 + BY2WD + BY2WD) +// #define Uoffset 8 + +// #define UREGSIZE (Ureg_r1 + 4 - Uoffset) /* this ought to work */ +#define UREGSIZE ((Ureg_r1+4-Uoffset) + 2*BY2V + 16 + BY2WD + BY2WD) +#define Uoffset 0 +#define Notuoffset 8 + +/* + * MMU + */ +#define PGSZ4K (0x00<<13) +#define PGSZ16K (0x03<<13) +#define PGSZ64K (0x0F<<13) +#define PGSZ256K (0x3F<<13) +#define PGSZ1M (0xFF<<13) +#define PGSZ4M (0x3FF<<13) +#define PGSZ8M (0x7FF<<13) /* not on loongson 2e */ +#define PGSZ16M (0xFFF<<13) +#define PGSZ64M (0x3FFF<<13) /* not on loongson 2e */ +#define PGSZ256M (0xFFFF<<13) /* not on loongson 2e */ + +/* mips address spaces, tlb-mapped unless marked otherwise */ +#define KUSEG 0x00000000 /* user process */ +#define KSEG0 0x80000000 /* kernel (direct mapped, cached) */ +#define KSEG1 0xA0000000 /* kernel (direct mapped, uncached: i/o) */ +#define KSEG2 0xC0000000 /* kernel, used for TSTKTOP */ +#define KSEG3 0xE0000000 /* kernel, used by kmap */ +#define KSEGM 0xE0000000 /* mask to check which seg */ + +/* + * Fundamental addresses + */ + +#define CONFADDR (KZERO+0x1000) /* just above vectors, only for reboot */ +#define CONFARGV (KZERO+0x2000) /* used by assembler */ +#define REBOOTADDR (KZERO+0x3000) +#define MACHADDR (KTZERO-MAXMACH*MACHSIZE) /* Mach structures */ +#define MACHP(n) ((Mach *)(MACHADDR+(n)*MACHSIZE)) +#define ROM 0xbfc00000 +#define KMAPADDR 0xE0000000 /* kmap'd addresses */ +#define WIREDADDR 0xE2000000 /* address wired kernel space */ + +#define PHYSCONS (KSEG1|0x1fd003f8) /* i8250 uart */ + +#define PIDXSHFT 12 +#ifndef BIGPAGES +#define NCOLOR 8 +#define PIDX ((NCOLOR-1)<>PIDXSHFT) % NCOLOR) +#else +/* no cache aliases are possible with pages of 16K or larger */ +#define NCOLOR 1 +#define PIDX 0 +#define getpgcolor(a) 0 +#endif +#define KMAPSHIFT 15 + +#define PTEGLOBL (1<<0) +#define PTEVALID (1<<1) +#define PTEWRITE (1<<2) +#define PTERONLY 0 +#define PTEALGMASK (7<<3) +#define PTENONCOHERWT (0<<3) /* cached, write-through (slower) */ +#define PTEUNCACHED (2<<3) +#define PTENONCOHERWB (3<<3) /* cached, write-back */ +#define PTEUNCACHEDACC (7<<3) +/* rest are reserved on 24k */ +#define PTECOHERXCL (4<<3) +#define PTECOHERXCLW (5<<3) +#define PTECOHERUPDW (6<<3) + +#define PTECACHABILITY PTENONCOHERWB /* loongson 2E only allows this */ + +#define PTEPID(n) (n) +#define PTEMAPMEM (1024*1024) +#define PTEPERTAB (PTEMAPMEM/BY2PG) +#define SEGMAPSIZE 512 +#define SSEGMAPSIZE 16 + +#define STLBLOG 15 +#define STLBSIZE (1< init.h + +reboot.h: initreboot.s rebootcode.c mem.h $objtype.s /$objtype/lib/libc.a + $AS $AFLAGS initreboot.s + $CC $CFLAGS rebootcode.c + # -lc is only for memmove + $LD -l -a -s -T$REBOOTADDR -R4 -o reboot.out initreboot.$O 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 + +l.$O: $objtype.s +main.$O: errstr.h init.h reboot.h +devether.$O: ../port/netif.h etherif.h +archln.$O clock.$O faultmips.$O mmu.$O trap.$O: /$objtype/include/ureg.h + +%.clean:V: + rm -f $stem.c [9bz]$stem [9bz]$stem.gz boot$stem.* *.list + +# override ../port/portmkfile +# create /boot/boot +boot$CONF.out: $CONF print.$O $BOOTDIR/boot.c $BOOTLIB + $BOOTDIR/mkboot $CONF > boot$CONF.c + $CC $CFLAGS boot$CONF.c + $CC $CFLAGS ../boot/printstub.c + $AS c_fcr0.s + $LD -a -o boot$CONF.out -T$UTZERO -R$MAXBY2PG boot$CONF.$O $BOOTLIB printstub.$O c_fcr0.$O > boot$CONF.list --- /dev/null +++ /sys/src/9/loongson/mmu.c @@ -0,0 +1,494 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "ureg.h" + +#define TLBINVAL(x, pid) puttlbx(x, KZERO|((x)<<(PGSHIFT+1))|(pid), 0, 0, PGSZ) + +enum { + Debugswitch = 0, + Debughash = 0, +}; + +static ulong ktime[8]; /* only for first 8 cpus */ + +void +tlbinit(void) +{ + int i; + + for(i=0; inext = k+1; + k->next = 0; + unlock(&kmaplock); + + m->ktlbnext = KTLBOFF; +} + +void +kmapinval(void) +{ + int mno, i, curpid; + KMap *k, *next; + uchar *ktlbx; + + if(m->machno < nelem(ktime)) + ktime[m->machno] = MACHP(0)->ticks; + if(m->kactive == 0) + return; + + curpid = PTEPID(TLBPID(tlbvirt())); + ktlbx = m->ktlbx; + for(i = 0; i < NTLB; i++, ktlbx++){ + if(*ktlbx == 0) + continue; + TLBINVAL(i, curpid); + *ktlbx = 0; + } + + mno = m->machno; + for(k = m->kactive; k; k = next) { + next = k->konmach[mno]; + kunmap(k); + } + + m->kactive = 0; + m->ktlbnext = KTLBOFF; +} + +static int +putktlb(KMap *k) +{ + int x; + ulong virt; + ulong tlbent[3]; + + virt = k->virt & ~BY2PG | TLBPID(tlbvirt()); + x = gettlbp(virt, tlbent); + if (!m->paststartup) + if (up) { /* startup just ended? */ + tlbroff = KTLBOFF; + setwired(tlbroff); + m->paststartup = 1; + } else if (x < 0) { /* no such entry? use next */ + x = m->ktlbnext++; + if(m->ktlbnext >= tlbroff) + m->ktlbnext = KTLBOFF; + } + if (x < 0) /* no entry for va? overwrite random one */ + x = puttlb(virt, k->phys0, k->phys1); + else + puttlbx(x, virt, k->phys0, k->phys1, PGSZ); + m->ktlbx[x] = 1; + return x; +} + +/* + * Arrange that the KMap'd virtual address will hit the same + * primary cache line as pg->va by making bits 14...12 of the + * tag the same as virtual address. These bits are the index + * into the primary cache and are checked whenever accessing + * the secondary cache through the primary. Violation causes + * a VCE trap. + */ +KMap * +kmap(Page *pg) +{ + int s, printed = 0; + ulong pte, virt; + KMap *k; + + s = splhi(); + lock(&kmaplock); + + if(kmapfree == 0) { +retry: + unlock(&kmaplock); + kmapinval(); /* try and free some */ + lock(&kmaplock); + if(kmapfree == 0){ + unlock(&kmaplock); + splx(s); + if(printed++ == 0){ + /* using iprint here we get mixed up with other prints */ + print("%d KMAP RETRY %#lux ktime %ld %ld %ld %ld %ld %ld %ld %ld\n", + m->machno, getcallerpc(&pg), + ktime[0], ktime[1], ktime[2], ktime[3], + ktime[4], ktime[5], ktime[6], ktime[7]); + delay(200); + } + splhi(); + lock(&kmaplock); + goto retry; + } + } + + k = kmapfree; + kmapfree = k->next; + + k->pg = pg; + /* + * One for the allocation, + * One for kactive + */ + k->pc = getcallerpc(&pg); + k->ref = 2; + k->konmach[m->machno] = m->kactive; + m->kactive = k; + + virt = pg->va; + /* bits 13..12 form the cache virtual index */ + virt &= PIDX; + virt |= KMAPADDR | ((k-kpte)<virt = virt; + pte = PPN(pg->pa)|PTECACHABILITY|PTEGLOBL|PTEWRITE|PTEVALID; + if(virt & BY2PG) { + k->phys0 = PTEGLOBL | PTECACHABILITY; + k->phys1 = pte; + } + else { + k->phys0 = pte; + k->phys1 = PTEGLOBL | PTECACHABILITY; + } + + putktlb(k); + unlock(&kmaplock); + + splx(s); + return k; +} + +void +kunmap(KMap *k) +{ + int s; + + s = splhi(); + if(decref(k) == 0) { + k->virt = 0; + k->phys0 = 0; + k->phys1 = 0; + k->pg = 0; + + lock(&kmaplock); + k->next = kmapfree; + kmapfree = k; +//nfree(); + unlock(&kmaplock); + } + splx(s); +} + +void +kfault(Ureg *ur) /* called from trap() */ +{ + ulong index, addr; + KMap *k, *f; + + addr = ur->badvaddr; + index = (addr & ~KSEGM) >> KMAPSHIFT; + if(index >= KPTESIZE) + panic("kmapfault: %#lux", addr); + + k = &kpte[index]; + if(k->virt == 0) + panic("kmapfault: unmapped %#lux", addr); + + for(f = m->kactive; f; f = f->konmach[m->machno]) + if(f == k) + break; + if(f == 0) { + incref(k); + k->konmach[m->machno] = m->kactive; + m->kactive = k; + } + putktlb(k); +} + +struct +{ + ulong va; + ulong pl; + ulong ph; +} wired[NWTLB+1]; /* +1 to avoid zero size if NWTLB==0 */ + +void +machwire(void) +{ + int i; + + //if(m->machno == 0) + // return; + for(i = 0; i < NWTLB; i++) + if(wired[i].va) + puttlbx(i+WTLBOFF, wired[i].va, wired[i].pl, + wired[i].ph, PGSZ); +} + +/* + * Process must be splhi + */ +int +newtlbpid(Proc *p) +{ + int i, s; + Proc **h; + + i = m->lastpid; + h = m->pidproc; + for(s = 0; s < NTLBPID; s++) { + i++; + if(i >= NTLBPID) + i = 1; + if(h[i] == 0) + break; + } + + if(h[i]) + purgetlb(i); + if(h[i] != 0) + panic("newtlb"); + + m->pidproc[i] = p; + p->pidonmach[m->machno] = i; + m->lastpid = i; + + return i; +} + +void +mmuswitch(Proc *p) +{ + int tp; + static char lasttext[32]; + + if(Debugswitch && !p->kp){ + if(strncmp(lasttext, p->text, sizeof lasttext) != 0) + iprint("[%s]", p->text); + strncpy(lasttext, p->text, sizeof lasttext); + } + + if(p->newtlb) { + memset(p->pidonmach, 0, sizeof p->pidonmach); + p->newtlb = 0; + } + tp = p->pidonmach[m->machno]; + if(tp == 0) + tp = newtlbpid(p); + puttlbx(0, KZERO|PTEPID(tp), 0, 0, PGSZ); +} + +void +mmurelease(Proc *p) +{ + memset(p->pidonmach, 0, sizeof p->pidonmach); +} + +void +putmmu(ulong tlbvirt, ulong tlbphys, Page *pg) +{ + short tp; + char *ctl; + Softtlb *entry; + int s; + + s = splhi(); + tp = up->pidonmach[m->machno]; + if(tp == 0) + tp = newtlbpid(up); + + tlbvirt |= PTEPID(tp); + if((tlbphys & PTEALGMASK) != PTEUNCACHED) { + tlbphys &= ~PTEALGMASK; + tlbphys |= PTECACHABILITY; + } + + entry = putstlb(tlbvirt, tlbphys); + puttlb(entry->virt, entry->phys0, entry->phys1); + + ctl = &pg->cachectl[m->machno]; + switch(*ctl) { + case PG_TXTFLUSH: + icflush((void*)pg->va, BY2PG); + *ctl = PG_NOFLUSH; + break; + case PG_DATFLUSH: + dcflush((void*)pg->va, BY2PG); + *ctl = PG_NOFLUSH; + break; + case PG_NEWCOL: + cleancache(); /* Too expensive */ + *ctl = PG_NOFLUSH; + break; + } + splx(s); +} + +void +purgetlb(int pid) +{ + int i, mno; + Proc *sp, **pidproc; + Softtlb *entry, *etab; + + m->tlbpurge++; + + /* + * find all pid entries that are no longer used by processes + */ + mno = m->machno; + pidproc = m->pidproc; + for(i=1; ipidonmach[mno] != i) + pidproc[i] = 0; + } + + /* + * shoot down the one we want + */ + sp = pidproc[pid]; + if(sp != 0) + sp->pidonmach[mno] = 0; + pidproc[pid] = 0; + + /* + * clean out all dead pids from the stlb; + */ + entry = m->stb; + for(etab = &entry[STLBSIZE]; entry < etab; entry++) + if(pidproc[TLBPID(entry->virt)] == 0) + entry->virt = 0; + + /* + * clean up the hardware + */ + for(i=tlbroff; inewtlb = 1; + mmuswitch(up); + splx(s); +} + +/* tlbvirt also has TLBPID() in its low byte as the asid */ +Softtlb* +putstlb(ulong tlbvirt, ulong tlbphys) +{ + int odd; + Softtlb *entry; + + /* identical calculation in l.s/utlbmiss */ + entry = &m->stb[stlbhash(tlbvirt)]; + odd = tlbvirt & BY2PG; /* even/odd bit */ + tlbvirt &= ~BY2PG; /* zero even/odd bit */ + if(entry->virt != tlbvirt) { /* not my entry? overwrite it */ + if(entry->virt != 0) { + m->hashcoll++; + if (Debughash) + iprint("putstlb: hash collision: %#lx old virt " + "%#lux new virt %#lux page %#lux\n", + entry - m->stb, entry->virt, tlbvirt, + tlbvirt >> (PGSHIFT+1)); + } + entry->virt = tlbvirt; + entry->phys0 = 0; + entry->phys1 = 0; + } + + if(odd) + entry->phys1 = tlbphys; + else + entry->phys0 = tlbphys; + + if(entry->phys0 == 0 && entry->phys1 == 0) + entry->virt = 0; + + return entry; +} + +void +checkmmu(ulong, ulong) +{ +} + +void +countpagerefs(ulong*, int) +{ +} + +/* + * 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 >= KZERO || pa >= memsize) + return 0; + return memsize - pa; +} + +/* print tlb entries for debug */ +#define TLBPHYS(x) ((((x)&~0x3f)<<6) | ((x)&0x3f)) /* phys addr & flags */ + +void +dumptlb(void) +{ + Softtlb entry; + int i; + + iprint("dump tlb\n"); + for(i=0; istb[i]; + if(entry->virt != 0) + iprint("stlb index %2d, virt %.8lux, phys0 %.8lux, phys1 %.8lux\n", + i, entry->virt, TLBPHYS(entry->phys0), TLBPHYS(entry->phys1)); + } +} --- /dev/null +++ /sys/src/9/loongson/mouse.c @@ -0,0 +1,346 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" +#include "io.h" + +#define Image IMAGE +#include +#include +#include +#include "screen.h" + +/* + * mouse types + */ +enum +{ + Mouseother= 0, + Mouseserial= 1, + MousePS2= 2, +}; + +extern int mouseshifted; + +static QLock mousectlqlock; +static int mousetype; +static int intellimouse; +static int packetsize; +static int resolution; +static int accelerated; +static int mousehwaccel; +static char mouseport[5]; + +enum +{ + CMaccelerated, + CMhwaccel, + CMintellimouse, + CMlinear, + CMps2, + CMps2intellimouse, + CMres, + CMreset, + CMserial, +}; + +static Cmdtab mousectlmsg[] = +{ + CMaccelerated, "accelerated", 0, + CMhwaccel, "hwaccel", 2, + CMintellimouse, "intellimouse", 1, + CMlinear, "linear", 1, + CMps2, "ps2", 1, + CMps2intellimouse, "ps2intellimouse", 1, + CMres, "res", 0, + CMreset, "reset", 1, + CMserial, "serial", 0, +}; + +/* + * ps/2 mouse message is three bytes + * + * byte 0 - 0 0 SDY SDX 1 M R L + * byte 1 - DX + * byte 2 - DY + * + * shift & right button is the same as middle button + * + * Intellimouse and AccuPoint with extra buttons deliver + * byte 3 - 00 or 01 or FF according to extra button state. + * extra buttons are mapped in this code to buttons 4 and 5. + * AccuPoint generates repeated events for these buttons; +* it and Intellimouse generate 'down' events only, so + * user-level code is required to generate button 'up' events + * if they are needed by the application. + * Also on laptops with AccuPoint AND external mouse, the + * controller may deliver 3 or 4 bytes according to the type + * of the external mouse; code must adapt. + * + * On the NEC Versa series (and perhaps others?) we seem to + * lose a byte from the packet every once in a while, which + * means we lose where we are in the instruction stream. + * To resynchronize, if we get a byte more than two seconds + * after the previous byte, we assume it's the first in a packet. + */ +static void +ps2mouseputc(int c, int shift) +{ + static short msg[4]; + static int nb; + static uchar b[] = {0, 1, 4, 5, 2, 3, 6, 7, 0, 1, 2, 3, 2, 3, 6, 7 }; + static ulong lasttick; + ulong m; + int buttons, dx, dy; + + /* + * non-ps2 keyboards might not set shift + * but still set mouseshifted. + */ + shift |= mouseshifted; + /* + * Resynchronize in stream with timing; see comment above. + */ + m = MACHP(0)->ticks; + if(TK2SEC(m - lasttick) > 2) + nb = 0; + lasttick = m; + + /* + * check byte 0 for consistency + */ + if(nb==0 && (c&0xc8)!=0x08) + if(intellimouse && (c==0x00 || c==0x01 || c==0xFF)){ + /* last byte of 4-byte packet */ + packetsize = 4; + return; + } + + msg[nb] = c; + if(++nb == packetsize){ + nb = 0; + if(msg[0] & 0x10) + msg[1] |= 0xFF00; + if(msg[0] & 0x20) + msg[2] |= 0xFF00; + + buttons = b[(msg[0]&7) | (shift ? 8 : 0)]; + if(intellimouse && packetsize==4){ + if((msg[3]&0xc8) == 0x08){ + /* first byte of 3-byte packet */ + packetsize = 3; + msg[0] = msg[3]; + nb = 1; + /* fall through to emit previous packet */ + }else{ + /* The AccuPoint on the Toshiba 34[48]0CT + * encodes extra buttons as 4 and 5. They repeat + * and don't release, however, so user-level + * timing code is required. Furthermore, + * intellimice with 3buttons + scroll give a + * two's complement number in the lower 4 bits + * (bit 4 is sign extension) that describes + * the amount the scroll wheel has moved during + * the last sample. Here we use only the sign to + * decide whether the wheel is moving up or down + * and generate a single button 4 or 5 click + * accordingly. + */ + if((msg[3] >> 3) & 1) + buttons |= 1<<3; + else if(msg[3] & 0x7) + buttons |= 1<<4; + } + } + dx = msg[1]; + dy = -msg[2]; + mousetrack(dx, dy, buttons, TK2MS(MACHP(0)->ticks)); + } + return; +} + +/* + * set up a ps2 mouse + */ +static void +ps2mouse(void) +{ + if(mousetype == MousePS2) + return; + + i8042auxenable(ps2mouseputc); + /* make mouse streaming, enabled */ + i8042auxcmd(0xEA); + i8042auxcmd(0xF4); + + mousetype = MousePS2; + packetsize = 3; + mousehwaccel = 1; +} + +/* + * The PS/2 Trackpoint multiplexor on the IBM Thinkpad T23 ignores + * acceleration commands. It is supposed to pass them on + * to the attached device, but my Logitech mouse is simply + * not behaving any differently. For such devices, we allow + * the user to use "hwaccel off" to tell us to back off to + * software acceleration even if we're using the PS/2 port. + * (Serial mice are always software accelerated.) + * For more information on the Thinkpad multiplexor, see + * http://wwwcssrv.almaden.ibm.com/trackpoint/ + */ +static void +setaccelerated(int x) +{ + accelerated = x; + if(mousehwaccel){ + switch(mousetype){ + case MousePS2: + i8042auxcmd(0xE7); + return; + } + } + mouseaccelerate(x); +} + +static void +setlinear(void) +{ + accelerated = 0; + if(mousehwaccel){ + switch(mousetype){ + case MousePS2: + i8042auxcmd(0xE6); + return; + } + } + mouseaccelerate(0); +} + +static void +setres(int n) +{ + resolution = n; + switch(mousetype){ + case MousePS2: + i8042auxcmd(0xE8); + i8042auxcmd(n); + break; + } +} + +static void +setintellimouse(void) +{ + intellimouse = 1; + packetsize = 4; + switch(mousetype){ + case MousePS2: + i8042auxcmd(0xF3); /* set sample */ + i8042auxcmd(0xC8); + i8042auxcmd(0xF3); /* set sample */ + i8042auxcmd(0x64); + i8042auxcmd(0xF3); /* set sample */ + i8042auxcmd(0x50); + break; + case Mouseserial: +// i8250setmouseputc(mouseport, m5mouseputc); + break; + } +} + +static void +resetmouse(void) +{ + packetsize = 3; + switch(mousetype){ + case MousePS2: + i8042auxcmd(0xF6); + i8042auxcmd(0xEA); /* streaming */ + i8042auxcmd(0xE8); /* set resolution */ + i8042auxcmd(3); + i8042auxcmd(0xF4); /* enabled */ + break; + } +} + +void +mousectl(Cmdbuf *cb) +{ + Cmdtab *ct; + + qlock(&mousectlqlock); + if(waserror()){ + qunlock(&mousectlqlock); + nexterror(); + } + + ct = lookupcmd(cb, mousectlmsg, nelem(mousectlmsg)); + switch(ct->index){ + case CMaccelerated: + setaccelerated(cb->nf == 1 ? 1 : atoi(cb->f[1])); + break; + case CMintellimouse: + setintellimouse(); + break; + case CMlinear: + setlinear(); + break; + case CMps2: + intellimouse = 0; + ps2mouse(); + break; + case CMps2intellimouse: + ps2mouse(); + setintellimouse(); + break; + case CMres: + if(cb->nf >= 2) + setres(atoi(cb->f[1])); + else + setres(1); + break; + case CMreset: + resetmouse(); + if(accelerated) + setaccelerated(accelerated); + if(resolution) + setres(resolution); + if(intellimouse) + setintellimouse(); + break; + case CMserial: + error("serial mice not supported"); + break; +/* if(mousetype == Mouseserial) + error(Emouseset); + + if(cb->nf > 2){ + if(strcmp(cb->f[2], "M") == 0) + i8250mouse(cb->f[1], m3mouseputc, 0); + else if(strcmp(cb->f[2], "MI") == 0) + i8250mouse(cb->f[1], m5mouseputc, 0); + else + i8250mouse(cb->f[1], mouseputc, cb->nf == 1); + } else + i8250mouse(cb->f[1], mouseputc, cb->nf == 1); + + mousetype = Mouseserial; + strncpy(mouseport, cb->f[1], sizeof(mouseport)-1); + packetsize = 3; + break; +*/ + case CMhwaccel: + if(strcmp(cb->f[1], "on")==0) + mousehwaccel = 1; + else if(strcmp(cb->f[1], "off")==0) + mousehwaccel = 0; + else + cmderror(cb, "bad mouse control message"); + } + + qunlock(&mousectlqlock); + poperror(); +} --- /dev/null +++ /sys/src/9/loongson/pci.c @@ -0,0 +1,664 @@ +/* + * PCI support code. + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +typedef struct Pci Pci; + +struct Pci { + ulong id; + ulong cs; + ulong revclass; + ulong misc; /* cache line size, latency timer, header type, bist */ + ulong base[6]; /* base addr regs */ + ulong unused[5]; + ulong intr; + ulong mask[6]; + ulong trans[6]; +}; + +enum { + /* cs bits */ + CIoEn = (1<<0), + CMemEn = (1<<1), + CMasEn = (1<<2), + CSpcEn = (1<<4), + CParEn = (1<<6), + CSErrEn = (1<<8), + + SMasTgtAb = (1<<24), /* master target abort */ + SMasAb = (1<<25), /* master abort */ +}; + +enum { + MaxFNO = 7, + MaxUBN = 255, +}; + +enum +{ /* command register */ + IOen = (1<<0), + MEMen = (1<<1), + MASen = (1<<2), + MemWrInv = (1<<4), + PErrEn = (1<<6), + SErrEn = (1<<8), +}; + +static Lock pcicfglock; +static Lock pcicfginitlock; +static int pcicfgmode = -1; +static int pcimaxbno = 0; +static int pcimaxdno; +static Pcidev *pciroot; +static Pcidev *pcilist; +static Pcidev *pcitail; +static Pci *pci = (Pci*)PCICFG; + +static int pcicfgrw8(int, int, int, int); +static int pcicfgrw16(int, int, int, int); +static int pcicfgrw32(int, int, int, int); + +static char* bustypes[] = { + "CBUSI", + "CBUSII", + "EISA", + "FUTURE", + "INTERN", + "ISA", + "MBI", + "MBII", + "MCA", + "MPI", + "MPSA", + "NUBUS", + "PCI", + "PCMCIA", + "TC", + "VL", + "VME", + "XPRESS", +}; + +static int +tbdffmt(Fmt* fmt) +{ + char *p; + int l, r; + uint type, tbdf; + + if((p = malloc(READSTR)) == nil) + return fmtstrcpy(fmt, "(tbdfconv)"); + + switch(fmt->r){ + case 'T': + tbdf = va_arg(fmt->args, int); + if(tbdf == BUSUNKNOWN) + snprint(p, READSTR, "unknown"); + else{ + type = BUSTYPE(tbdf); + if(type < nelem(bustypes)) + l = snprint(p, READSTR, bustypes[type]); + else + l = snprint(p, READSTR, "%d", type); + snprint(p+l, READSTR-l, ".%d.%d.%d", + BUSBNO(tbdf), BUSDNO(tbdf), BUSFNO(tbdf)); + } + break; + + default: + snprint(p, READSTR, "(tbdfconv)"); + break; + } + r = fmtstrcpy(fmt, p); + free(p); + + return r; +} + +ulong +pcibarsize(Pcidev *p, int rno) +{ + ulong v, size; + + v = pcicfgrw32(p->tbdf, rno, 0, 1); + pcicfgrw32(p->tbdf, rno, 0xFFFFFFF0, 0); + size = pcicfgrw32(p->tbdf, rno, 0, 1); + if(v & 1) + size |= 0xFFFF0000; + pcicfgrw32(p->tbdf, rno, v, 0); + + return -(size & ~0x0F); +} + +static int +pcilscan(int bno, Pcidev** list) +{ + Pcidev *p, *head, *tail; + int dno, fno, i, hdt, l, maxfno, maxubn, rno, sbn, tbdf, ubn; + + maxubn = bno; + head = nil; + tail = nil; + /* dno from 5 due to its address mode */ + for(dno = 5; dno <= pcimaxdno; dno++){ + maxfno = 0; + for(fno = 0; fno <= maxfno; fno++){ + /* + * For this possible device, form the + * bus+device+function triplet needed to address it + * and try to read the vendor and device ID. + * If successful, allocate a device struct and + * start to fill it in with some useful information + * from the device's configuration space. + */ + tbdf = MKBUS(BusPCI, bno, dno, fno); + l = pcicfgrw32(tbdf, PciVID, 0, 1); + if(l == 0xFFFFFFFF || l == 0) + continue; + p = malloc(sizeof(*p)); + if(p == nil) + panic("pcilscan: no memory"); + p->tbdf = tbdf; + p->vid = l; + p->did = l>>16; + + if(pcilist != nil) + pcitail->list = p; + else + pcilist = p; + pcitail = p; + + p->pcr = pcicfgr16(p, PciPCR); + p->rid = pcicfgr8(p, PciRID); + p->ccrp = pcicfgr8(p, PciCCRp); + p->ccru = pcicfgr8(p, PciCCRu); + p->ccrb = pcicfgr8(p, PciCCRb); + p->cls = pcicfgr8(p, PciCLS); + p->ltr = pcicfgr8(p, PciLTR); + + p->intl = pcicfgr8(p, PciINTL); + p->intp = pcicfgr8(p, PciINTP); + + /* + * If the device is a multi-function device adjust the + * loop count so all possible functions are checked. + */ + hdt = pcicfgr8(p, PciHDT); + if(hdt & 0x80) + maxfno = MaxFNO; + + /* + * If appropriate, read the base address registers + * and work out the sizes. + */ + switch(p->ccrb) { + case 0x03: /* display controller */ + /* fall through */ + case 0x01: /* mass storage controller */ + case 0x02: /* network controller */ + case 0x04: /* multimedia device */ + case 0x07: /* simple comm. controllers */ + case 0x08: /* base system peripherals */ + case 0x09: /* input devices */ + case 0x0A: /* docking stations */ + case 0x0B: /* processors */ + case 0x0C: /* serial bus controllers */ + if((hdt & 0x7F) != 0) + break; + rno = PciBAR0 - 4; + for(i = 0; i < nelem(p->mem); i++) { + rno += 4; + p->mem[i].bar = pcicfgr32(p, rno); + p->mem[i].size = pcibarsize(p, rno); + } + break; + + case 0x00: + case 0x05: /* memory controller */ + case 0x06: /* bridge device */ + default: + break; + } + + if(head != nil) + tail->link = p; + else + head = p; + tail = p; + } + } + + *list = head; + for(p = head; p != nil; p = p->link){ + /* + * Find PCI-PCI bridges and recursively descend the tree. + */ + if(p->ccrb != 0x06 || p->ccru != 0x04) + continue; + + /* + * If the secondary or subordinate bus number is not + * initialised try to do what the PCI BIOS should have + * done and fill in the numbers as the tree is descended. + * On the way down the subordinate bus number is set to + * the maximum as it's not known how many buses are behind + * this one; the final value is set on the way back up. + */ + sbn = pcicfgr8(p, PciSBN); + ubn = pcicfgr8(p, PciUBN); + + if(sbn == 0 || ubn == 0) { + sbn = maxubn+1; + /* + * Make sure memory, I/O and master enables are + * off, set the primary, secondary and subordinate + * bus numbers and clear the secondary status before + * attempting to scan the secondary bus. + * + * Initialisation of the bridge should be done here. + */ + pcicfgw32(p, PciPCR, 0xFFFF0000); + l = (MaxUBN<<16)|(sbn<<8)|bno; + pcicfgw32(p, PciPBN, l); + pcicfgw16(p, PciSPSR, 0xFFFF); + maxubn = pcilscan(sbn, &p->bridge); + l = (maxubn<<16)|(sbn<<8)|bno; + + pcicfgw32(p, PciPBN, l); + } + else { + if(ubn > maxubn) + maxubn = ubn; + pcilscan(sbn, &p->bridge); + } + } + + return maxubn; +} + +static void +pcicfginit(void) +{ + char *p; + int n, bno; + Pcidev **list; + + lock(&pcicfginitlock); + if(pcicfgmode != -1) { + unlock(&pcicfginitlock); + return; + } + + pcicfgmode = 1; + pcimaxdno = 19; + + fmtinstall('T', tbdffmt); + + if(p = getconf("*pcimaxbno")){ + n = strtoul(p, 0, 0); + if(n < pcimaxbno) + pcimaxbno = n; + } + if(p = getconf("*pcimaxdno")){ + n = strtoul(p, 0, 0); + if(n < pcimaxdno) + pcimaxdno = n; + } + + list = &pciroot; + for(bno = 0; bno <= pcimaxbno; bno++){ + bno = pcilscan(bno, list); + while(*list) + list = &(*list)->link; + } + unlock(&pcicfginitlock); +} + +/* map the devince's cfg space and calculate the address */ +static void* +pcidevcfgaddr(int tbdf, int rno) +{ + ulong addr; + ulong b, d, f, type; + + b = BUSBNO(tbdf); + d = BUSDNO(tbdf); + f = BUSFNO(tbdf); + if(b == 0) { + /* Type 0 configuration on onboard PCI bus */ + addr = (1<<(d+11))|(f<<8)|rno; + type = 0x00000; + } else { + /* Type 1 configuration on offboard PCI bus */ + addr = (b<<16)|(d<<11)|(f<<8)|rno; + type = 0x10000; + } + + /* clear aborts */ + pci->cs |= SMasAb | SMasTgtAb; + + /* config map cfg reg to map the device's cfg space */ + *Pcimapcfg = (addr>>16)|type; + + return (void*)(PCIDEVCFG+(addr&0xffff)); +} + +static int +pcicfgrw8(int tbdf, int rno, int data, int read) +{ + int x; + void *addr; + + if(pcicfgmode == -1) + pcicfginit(); + + x = -1; + if(BUSDNO(tbdf) > pcimaxdno) + return x; + + lock(&pcicfglock); + addr = pcidevcfgaddr(tbdf, rno); + if(read) + x = *(uchar*)addr; + else + *(uchar*)addr = data; + unlock(&pcicfglock); + + return x; +} + +int +pcicfgr8(Pcidev* pcidev, int rno) +{ + return pcicfgrw8(pcidev->tbdf, rno, 0, 1); +} + +void +pcicfgw8(Pcidev* pcidev, int rno, int data) +{ + pcicfgrw8(pcidev->tbdf, rno, data, 0); +} + +static int +pcicfgrw16(int tbdf, int rno, int data, int read) +{ + int x; + void *addr; + + if(pcicfgmode == -1) + pcicfginit(); + + x = -1; + if(BUSDNO(tbdf) > pcimaxdno) + return x; + + lock(&pcicfglock); + addr = pcidevcfgaddr(tbdf, rno); + if(read) + x = *(ushort*)addr; + else + *(ushort*)addr = data; + unlock(&pcicfglock); + + return x; +} + +int +pcicfgr16(Pcidev* pcidev, int rno) +{ + return pcicfgrw16(pcidev->tbdf, rno, 0, 1); +} + +void +pcicfgw16(Pcidev* pcidev, int rno, int data) +{ + pcicfgrw16(pcidev->tbdf, rno, data, 0); +} + +static int +pcicfgrw32(int tbdf, int rno, int data, int read) +{ + int x; + void *addr; + + if(pcicfgmode == -1) + pcicfginit(); + + x = -1; + if(BUSDNO(tbdf) > pcimaxdno) + return x; + + lock(&pcicfglock); + addr = pcidevcfgaddr(tbdf, rno); + if(read) + x = *(ulong*)addr; + else + *(ulong*)addr = data; + unlock(&pcicfglock); + + return x; +} + +int +pcicfgr32(Pcidev* pcidev, int rno) +{ + return pcicfgrw32(pcidev->tbdf, rno, 0, 1); +} + +void +pcicfgw32(Pcidev* pcidev, int rno, int data) +{ + pcicfgrw32(pcidev->tbdf, rno, data, 0); +} + +Pcidev* +pcimatch(Pcidev* prev, int vid, int did) +{ + if(pcicfgmode == -1) + pcicfginit(); + + if(prev == nil) + prev = pcilist; + else + prev = prev->list; + + while(prev != nil){ + if((vid == 0 || prev->vid == vid) + && (did == 0 || prev->did == did)) + break; + prev = prev->list; + } + return prev; +} + +Pcidev* +pcimatchtbdf(int tbdf) +{ + Pcidev *pcidev; + + if(pcicfgmode == -1) + pcicfginit(); + + for(pcidev = pcilist; pcidev != nil; pcidev = pcidev->list) { + if(pcidev->tbdf == tbdf) + break; + } + return pcidev; +} + +void +pcireset(void) +{ + Pcidev *p; + + if(pcicfgmode == -1) + pcicfginit(); + + for(p = pcilist; p != nil; p = p->list) { + /* don't mess with the bridges */ + if(p->ccrb == 0x06) + continue; + pciclrbme(p); + } +} + +void +pcisetioe(Pcidev* p) +{ + p->pcr |= IOen; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pciclrioe(Pcidev* p) +{ + p->pcr &= ~IOen; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pcisetbme(Pcidev* p) +{ + p->pcr |= MASen; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pciclrbme(Pcidev* p) +{ + p->pcr &= ~MASen; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pcisetmwi(Pcidev* p) +{ + p->pcr |= MemWrInv; + pcicfgw16(p, PciPCR, p->pcr); +} + +void +pciclrmwi(Pcidev* p) +{ + p->pcr &= ~MemWrInv; + pcicfgw16(p, PciPCR, p->pcr); +} + +static int +pcigetpmrb(Pcidev* p) +{ + int ptr; + + if(p->pmrb != 0) + return p->pmrb; + p->pmrb = -1; + + /* + * If there are no extended capabilities implemented, + * (bit 4 in the status register) assume there's no standard + * power management method. + * Find the capabilities pointer based on PCI header type. + */ + if(!(pcicfgr16(p, PciPSR) & 0x0010)) + return -1; + switch(pcicfgr8(p, PciHDT)){ + default: + return -1; + case 0: /* all other */ + case 1: /* PCI to PCI bridge */ + ptr = 0x34; + break; + case 2: /* CardBus bridge */ + ptr = 0x14; + break; + } + ptr = pcicfgr32(p, ptr); + + while(ptr != 0){ + /* + * Check for validity. + * Can't be in standard header and must be double + * word aligned. + */ + if(ptr < 0x40 || (ptr & ~0xFC)) + return -1; + if(pcicfgr8(p, ptr) == 0x01){ + p->pmrb = ptr; + return ptr; + } + + ptr = pcicfgr8(p, ptr+1); + } + + return -1; +} + +int +pcigetpms(Pcidev* p) +{ + int pmcsr, ptr; + + if((ptr = pcigetpmrb(p)) == -1) + return -1; + + /* + * Power Management Register Block: + * offset 0: Capability ID + * 1: next item pointer + * 2: capabilities + * 4: control/status + * 6: bridge support extensions + * 7: data + */ + pmcsr = pcicfgr16(p, ptr+4); + + return pmcsr & 0x0003; +} + +int +pcisetpms(Pcidev* p, int state) +{ + int ostate, pmc, pmcsr, ptr; + + if((ptr = pcigetpmrb(p)) == -1) + return -1; + + pmc = pcicfgr16(p, ptr+2); + pmcsr = pcicfgr16(p, ptr+4); + ostate = pmcsr & 0x0003; + pmcsr &= ~0x0003; + + switch(state){ + default: + return -1; + case 0: + break; + case 1: + if(!(pmc & 0x0200)) + return -1; + break; + case 2: + if(!(pmc & 0x0400)) + return -1; + break; + case 3: + break; + } + pmcsr |= state; + pcicfgw16(p, ptr+4, pmcsr); + + return ostate; +} + +int +pcisubirq(int tbdf) +{ + return Pciintrbase + pcicfgrw8(tbdf, PciINTP, 0, 1); +} --- /dev/null +++ /sys/src/9/loongson/rebootcode.c @@ -0,0 +1,86 @@ +/* + * mips reboot trampoline code + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +#define csr8r(r) (((uchar*)PHYSCONS)[r]) +#define csr8o(r, v) (((uchar*)PHYSCONS)[r] = (v)) + +enum { /* i8250 registers */ + Thr = 0, /* Transmitter Holding (WO) */ + Lsr = 5, /* Line Status */ +}; +enum { /* Lsr */ + Thre = 0x20, /* Thr Empty */ +}; + +void putc(int); +void go(void*); +ulong _argc; +ulong _env; + +/* + * Copy the new kernel to its correct location in physical memory, + * flush caches, ignore TLBs (we're in KSEG0 space), and jump to + * the start of the kernel. + */ +void +main(ulong aentry, ulong acode, ulong asize, ulong argc) +{ + void *kernel; + static ulong entry, code, size; + + putc('B'); putc('o'); putc('o'); putc('t'); + /* copy args to heap before moving stack to before a.out header */ + entry = aentry; + code = acode; + size = asize; + _argc = argc; /* argc passed to kernel */ + _env = (ulong)&((char**)CONFARGV)[argc]; + setsp(entry-0x20-4); + + memmove((void *)entry, (void *)code, size); + + cleancache(); + coherence(); + + /* + * jump to kernel entry point. + */ + putc(' '); + kernel = (void*)entry; + go(kernel); /* off we go - never to return */ + + putc('?'); + putc('!'); + for(;;) + ; +} + +void +putc(int c) +{ + int i; + + for(i = 0; !(csr8r(Lsr) & Thre) && i < 1000000; i++) + ; + csr8o(Thr, (uchar)c); + for(i = 0; !(csr8r(Lsr) & Thre) && i < 1000000; i++) + ; +} + +long +syscall(Ureg*) +{ + return -1; +} + +void +trap(Ureg *) +{ +} --- /dev/null +++ /sys/src/9/loongson/screen.c @@ -0,0 +1,603 @@ +/* + * radeon frame buffer + * currently use all PMON defaults + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +#define Image IMAGE +#include +#include +#include +#include "screen.h" + +enum { + Tabstop = 4, + Scroll = 8, + Wid = 640, + Ht = 480, + Depth = 16, +}; + +Cursor arrow = { + { -1, -1 }, + { 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C, + 0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04, + 0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04, + 0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40, + }, + { 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0, + 0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8, + 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8, + 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00, + }, +}; + +Memimage *gscreen; + +static Memdata xgdata; + +static Memimage xgscreen = +{ + { 0, 0, Wid, Ht }, /* r */ + { 0, 0, Wid, Ht }, /* clipr */ + Depth, /* depth */ + 3, /* nchan */ + RGB16, /* chan */ + nil, /* cmap */ + &xgdata, /* data */ + 0, /* zero */ + 0, /* width in words of a single scan line */ + 0, /* layer */ + 0, /* flags */ +}; + +static Memimage *conscol; +static Memimage *back; +static Memsubfont *memdefont; + +static Lock screenlock; + +static Point curpos; +static int h, w; +static Rectangle window; + +int screenwid; +int screenht; +int screendepth; + +/* + * Software cursor. + */ +static int swvisible; /* is the cursor visible? */ +static int swenabled; /* is the cursor supposed to be on the screen? */ +static Memimage *swback; /* screen under cursor */ +static Memimage *swimg; /* cursor image */ +static Memimage *swmask; /* cursor mask */ +static Memimage *swimg1; +static Memimage *swmask1; + +static Point swoffset; +static Rectangle swrect; /* screen rectangle in swback */ +static Point swpt; /* desired cursor location */ +static Point swvispt; /* actual cursor location */ +static int swvers; /* incremented each time cursor image changes */ +static int swvisvers; /* the version on the screen */ + +/* + * called with drawlock locked for us, most of the time. + * kernel prints at inopportune times might mean we don't + * hold the lock, but memimagedraw is now reentrant so + * that should be okay: worst case we get cursor droppings. + */ +static void +swcursorhide(void) +{ + if(swvisible == 0) + return; + if(swback == nil) + return; + swvisible = 0; + memimagedraw(gscreen, swrect, swback, ZP, memopaque, ZP, S); + flushmemscreen(swrect); +} + +static void +swcursoravoid(Rectangle r) +{ + if(swvisible && rectXrect(r, swrect)) + swcursorhide(); +} + +static void +swcursordraw(void) +{ + int dounlock; + + if(swvisible) + return; + if(swenabled == 0) + return; + if(swback == nil || swimg1 == nil || swmask1 == nil) + return; + dounlock = canqlock(&drawlock); + swvispt = swpt; + swvisvers = swvers; + swrect = rectaddpt(Rect(0,0,16,16), swvispt); + memimagedraw(swback, swback->r, gscreen, swpt, memopaque, ZP, S); + memimagedraw(gscreen, swrect, swimg1, ZP, swmask1, ZP, SoverD); + flushmemscreen(swrect); + swvisible = 1; + if(dounlock) + qunlock(&drawlock); +} + +int +cursoron(int dolock) +{ + int retry; + + if (dolock) + lock(&cursor); + if (canqlock(&drawlock)) { + retry = 0; + swcursorhide(); + swcursordraw(); + qunlock(&drawlock); + } else + retry = 1; + if (dolock) + unlock(&cursor); + return retry; +} + +void +cursoroff(int dolock) +{ + if (dolock) + lock(&cursor); + swcursorhide(); + if (dolock) + unlock(&cursor); +} + +static void +swload(Cursor *curs) +{ + uchar *ip, *mp; + int i, j, set, clr; + + if(!swimg || !swmask || !swimg1 || !swmask1) + return; + /* + * Build cursor image and mask. + * Image is just the usual cursor image + * but mask is a transparent alpha mask. + * + * The 16x16x8 memimages do not have + * padding at the end of their scan lines. + */ + ip = byteaddr(swimg, ZP); + mp = byteaddr(swmask, ZP); + for(i=0; i<32; i++){ + set = curs->set[i]; + clr = curs->clr[i]; + for(j=0x80; j; j>>=1){ + *ip++ = set&j ? 0x00 : 0xFF; + *mp++ = (clr|set)&j ? 0xFF : 0x00; + } + } + swoffset = curs->offset; + swvers++; + memimagedraw(swimg1, swimg1->r, swimg, ZP, memopaque, ZP, S); + memimagedraw(swmask1, swmask1->r, swmask, ZP, memopaque, ZP, S); +} + +/* called from devmouse */ +void +setcursor(Cursor* curs) +{ + cursoroff(0); + swload(curs); + cursoron(0); +} + +static int +swmove(Point p) +{ + swpt = addpt(p, swoffset); + return 0; +} + +static void +swcursorclock(void) +{ + int x; + + if(!swenabled) + return; + swmove(mousexy()); + if(swvisible && eqpt(swpt, swvispt) && swvers==swvisvers) + return; + + x = splhi(); + if(swenabled) + if(!swvisible || !eqpt(swpt, swvispt) || swvers!=swvisvers) + if(canqlock(&drawlock)){ + swcursorhide(); + swcursordraw(); + qunlock(&drawlock); + } + splx(x); +} + +void +swcursorinit(void) +{ + static int init; + + if(!init){ + init = 1; + addclock0link(swcursorclock, 10); + swenabled = 1; + } + if(swback){ + freememimage(swback); + freememimage(swmask); + freememimage(swmask1); + freememimage(swimg); + freememimage(swimg1); + } + + swback = allocmemimage(Rect(0,0,32,32), gscreen->chan); + swmask = allocmemimage(Rect(0,0,16,16), GREY8); + swmask1 = allocmemimage(Rect(0,0,16,16), GREY1); + swimg = allocmemimage(Rect(0,0,16,16), GREY8); + swimg1 = allocmemimage(Rect(0,0,16,16), GREY1); + if(swback==nil || swmask==nil || swmask1==nil || swimg==nil || swimg1 == nil){ + print("software cursor: allocmemimage fails\n"); + return; + } + + memfillcolor(swmask, DOpaque); + memfillcolor(swmask1, DOpaque); + memfillcolor(swimg, DBlack); + memfillcolor(swimg1, DBlack); +} + +static int +screensize(void) +{ + char *p; + char *f[3]; + int width, height, depth; + + p = getconf("vgasize"); + if(p == nil || getfields(p, f, nelem(f), 0, "x") != nelem(f) || + (width = atoi(f[0])) < 16 || (height = atoi(f[1])) <= 0 || + (depth = atoi(f[2])) <= 0) { + width = screenwid? screenwid: Wid; + height = screenht? screenht: Ht; + depth = screendepth? screendepth: Depth; + } + + xgscreen.r.max = Pt(width, height); + xgscreen.depth = depth; + return 0; +} + +void +flushmemscreen(Rectangle) +{ +} + +static void +screenwin(void) +{ + char *greet; + Memimage *orange; + Point p, q; + Rectangle r; + + back = memwhite; + conscol = memblack; + + orange = allocmemimage(Rect(0, 0, 1, 1), RGB16); + orange->flags |= Frepl; + orange->clipr = gscreen->r; + orange->data->bdata[0] = 0x40; /* magic: colour? */ + orange->data->bdata[1] = 0xfd; /* magic: colour? */ + + w = memdefont->info[' '].width; + h = memdefont->height; + + r = insetrect(gscreen->r, 0); + + memimagedraw(gscreen, r, memblack, ZP, memopaque, ZP, S); + window = insetrect(r, 0); + memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S); + + memimagedraw(gscreen, Rect(window.min.x, window.min.y, + window.max.x, window.min.y + h + 5 + 6), orange, ZP, nil, ZP, S); + freememimage(orange); + window = insetrect(window, 5); + + greet = " Plan 9 Console "; + p = addpt(window.min, Pt(10, 0)); + q = memsubfontwidth(memdefont, greet); + memimagestring(gscreen, p, conscol, ZP, memdefont, greet); + flushmemscreen(r); + window.min.y += h + 6; + curpos = window.min; + window.max.y = window.min.y + ((window.max.y - window.min.y) / h) * h; +} + +static void +scroll(void) +{ + int o; + Point p; + Rectangle r; + + o = Scroll*h; + r = Rpt(window.min, Pt(window.max.x, window.max.y-o)); + p = Pt(window.min.x, window.min.y+o); + memimagedraw(gscreen, r, gscreen, p, nil, p, S); + flushmemscreen(r); + r = Rpt(Pt(window.min.x, window.max.y-o), window.max); + memimagedraw(gscreen, r, back, ZP, nil, ZP, S); + flushmemscreen(r); + + curpos.y -= o; +} + +static void +screenputc(char *buf) +{ + int w; + uint pos; + Point p; + Rectangle r; + static int *xp; + static int xbuf[256]; + + if (xp < xbuf || xp >= &xbuf[sizeof(xbuf)]) + xp = xbuf; + + switch (buf[0]) { + case '\n': + if (curpos.y + h >= window.max.y) + scroll(); + curpos.y += h; + screenputc("\r"); + break; + case '\r': + xp = xbuf; + curpos.x = window.min.x; + break; + case '\t': + p = memsubfontwidth(memdefont, " "); + w = p.x; + if (curpos.x >= window.max.x - Tabstop * w) + screenputc("\n"); + + pos = (curpos.x - window.min.x) / w; + pos = Tabstop - pos % Tabstop; + *xp++ = curpos.x; + r = Rect(curpos.x, curpos.y, curpos.x + pos * w, curpos.y + h); + memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S); + flushmemscreen(r); + curpos.x += pos * w; + break; + case '\b': + if (xp <= xbuf) + break; + xp--; + r = Rect(*xp, curpos.y, curpos.x, curpos.y + h); + memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S); + flushmemscreen(r); + curpos.x = *xp; + break; + case '\0': + break; + default: + p = memsubfontwidth(memdefont, buf); + w = p.x; + + if (curpos.x >= window.max.x - w) + screenputc("\n"); + + *xp++ = curpos.x; + r = Rect(curpos.x, curpos.y, curpos.x + w, curpos.y + h); + memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S); + memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf); + flushmemscreen(r); + curpos.x += w; + break; + } +} + +static void +myscreenputs(char *s, int n) +{ + int i; + Rune r; + char buf[4]; + + if(!islo()) { + /* don't deadlock trying to print in interrupt */ + if(!canlock(&screenlock)) + return; + } + else + lock(&screenlock); + + while(n > 0){ + i = chartorune(&r, s); + if(i == 0){ + s++; + --n; + continue; + } + memmove(buf, s, i); + buf[i] = 0; + n -= i; + s += i; + screenputc(buf); + } + unlock(&screenlock); +} + +void +screeninit(void) +{ + ulong chan; + uchar *fb; + + screensize(); + fb = (uchar*)fbinit(); + if(fb == nil){ + print("can't initialise %dx%dx%d framebuffer \n", + xgscreen.r.max.x, xgscreen.r.max.y, xgscreen.depth); + return; + } + + xgscreen.clipr = xgscreen.r; + switch(xgscreen.depth){ + default: + print("unsupported screen depth %d\n", xgscreen.depth); + xgscreen.depth = 16; + /* fall through */ + case 16: + chan = RGB16; + break; + case 24: + chan = BGR24; + break; + case 32: + chan = ARGB32; + break; + } + memsetchan(&xgscreen, chan); + conf.monitor = 1; + xgdata.bdata = fb; + xgdata.ref = 1; + gscreen = &xgscreen; + gscreen->width = wordsperline(gscreen->r, gscreen->depth); + + memimageinit(); + memdefont = getmemdefont(); + screenwin(); + screenputs = myscreenputs; +} + +uchar* +attachscreen(Rectangle *r, ulong *chan, int* d, int *width, int *softscreen) +{ + *r = gscreen->r; + *d = gscreen->depth; + *chan = gscreen->chan; + *width = gscreen->width; + *softscreen = 0; + + return gscreen->data->bdata; +} + +void +getcolor(ulong p, ulong *pr, ulong *pg, ulong *pb) +{ + USED(p, pr, pg, pb); +} + +int +setcolor(ulong p, ulong r, ulong g, ulong b) +{ + USED(p, r, g, b); + return 0; +} + +void +blankscreen(int blank) +{ + USED(blank); +} + +int +hwdraw(Memdrawparam *par) +{ + Memimage *dst, *src, *mask; + + if((dst=par->dst) == nil || dst->data == nil) + return 0; + if((src=par->src) == nil || src->data == nil) + return 0; + if((mask=par->mask) == nil || mask->data == nil) + return 0; + + if(dst->data->bdata == xgdata.bdata) + swcursoravoid(par->r); + if(src->data->bdata == xgdata.bdata) + swcursoravoid(par->sr); + if(mask->data->bdata == xgdata.bdata) + swcursoravoid(par->mr); + + return 0; +} + +/* radeon frame buffer control */ +typedef struct Ctlr { + Pcidev* pcidev; + int port; + void* mmio; + ulong paddr; + void* vaddr; +} Ctlr; + +static Ctlr *ctlr; + +static Pcidev* +videopci(void) +{ + static Pcidev *p = nil; + + while((p = pcimatch(p, 0, 0)) != nil){ + if(p->ccrb != Pcibcdisp || p->ccru != 0) + continue; + return p; + } + return nil; +} + +static void +fbenable(void) +{ + Pcidev *p; + + if(ctlr) + return; + p = videopci(); + if(p == nil) + return; + ctlr = malloc(sizeof(Ctlr)); + ctlr->pcidev = p; + ctlr->paddr = PCIMEMADDR(p->mem[0].bar & ~0x0f); + ctlr->vaddr = KSEG1ADDR(ctlr->paddr); + ctlr->port = p->mem[1].bar & ~0x01; + ctlr->mmio = KSEG1ADDR(PCIMEMADDR(p->mem[2].bar & ~0x0f)); +} + +void* +fbinit(void) +{ + if(!ctlr) + fbenable(); + + if(!ctlr) + return nil; + else + return ctlr->vaddr; +} --- /dev/null +++ /sys/src/9/loongson/screen.h @@ -0,0 +1,35 @@ +typedef struct Cursor Cursor; +typedef struct Cursorinfo Cursorinfo; +struct Cursorinfo { + Cursor; + Lock; +}; + +/* devmouse.c */ +extern void mousetrack(int, int, int, int); +extern Point mousexy(void); + +extern void mouseaccelerate(int); +extern int m3mouseputc(Queue*, int); +extern int m5mouseputc(Queue*, int); +extern int mouseputc(Queue*, int); + +extern Cursorinfo cursor; +extern Cursor arrow; + +/* mouse.c */ +extern void mousectl(Cmdbuf*); +extern void mouseresize(void); + +/* screen.c */ +extern void blankscreen(int); +extern void flushmemscreen(Rectangle); +extern uchar* attachscreen(Rectangle*, ulong*, int*, int*, int*); +extern int cursoron(int); +extern void cursoroff(int); +extern void setcursor(Cursor*); + +/* devdraw.c */ +extern QLock drawlock; + +#define ishwimage(i) 1 /* for ../port/devdraw.c */ --- /dev/null +++ /sys/src/9/loongson/sdata.c @@ -0,0 +1,2268 @@ +/* + * ATA (IDE) disk. + */ + +#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 "../port/sd.h" + +extern SDifc sdataifc; + +enum { + DbgCONFIG = 0x0001, /* detected drive config info */ + DbgIDENTIFY = 0x0002, /* detected drive identify info */ + DbgSTATE = 0x0004, /* dump state on panic */ + DbgPROBE = 0x0008, /* trace device probing */ + DbgDEBUG = 0x0080, /* the current problem... */ + DbgINL = 0x0100, /* That Inil20+ message we hate */ + Dbg48BIT = 0x0200, /* 48-bit LBA */ + DbgBsy = 0x0400, /* interrupt but Bsy (shared IRQ) */ +}; +#define DEBUG (DbgDEBUG|DbgSTATE) + +enum { /* I/O ports */ + Data = 0, + Error = 1, /* (read) */ + Features = 1, /* (write) */ + Count = 2, /* sector count<7-0>, sector count<15-8> */ + Ir = 2, /* interrupt reason (PACKET) */ + Sector = 3, /* sector number */ + Lbalo = 3, /* LBA<7-0>, LBA<31-24> */ + Cyllo = 4, /* cylinder low */ + Bytelo = 4, /* byte count low (PACKET) */ + Lbamid = 4, /* LBA<15-8>, LBA<39-32> */ + Cylhi = 5, /* cylinder high */ + Bytehi = 5, /* byte count hi (PACKET) */ + Lbahi = 5, /* LBA<23-16>, LBA<47-40> */ + Dh = 6, /* Device/Head, LBA<27-24> */ + Status = 7, /* (read) */ + Command = 7, /* (write) */ + + As = 2, /* Alternate Status (read) */ + Dc = 2, /* Device Control (write) */ +}; + +enum { /* Error */ + Med = 0x01, /* Media error */ + Ili = 0x01, /* command set specific (PACKET) */ + Nm = 0x02, /* No Media */ + Eom = 0x02, /* command set specific (PACKET) */ + Abrt = 0x04, /* Aborted command */ + Mcr = 0x08, /* Media Change Request */ + Idnf = 0x10, /* no user-accessible address */ + Mc = 0x20, /* Media Change */ + Unc = 0x40, /* Uncorrectable data error */ + Wp = 0x40, /* Write Protect */ + Icrc = 0x80, /* Interface CRC error */ +}; + +enum { /* Features */ + Dma = 0x01, /* data transfer via DMA (PACKET) */ + Ovl = 0x02, /* command overlapped (PACKET) */ +}; + +enum { /* Interrupt Reason */ + Cd = 0x01, /* Command/Data */ + Io = 0x02, /* I/O direction: read */ + Rel = 0x04, /* Bus Release */ +}; + +enum { /* Device/Head */ + Dev0 = 0xA0, /* Master */ + Dev1 = 0xB0, /* Slave */ + Lba = 0x40, /* LBA mode */ +}; + +enum { /* Status, Alternate Status */ + Err = 0x01, /* Error */ + Chk = 0x01, /* Check error (PACKET) */ + Drq = 0x08, /* Data Request */ + Dsc = 0x10, /* Device Seek Complete */ + Serv = 0x10, /* Service */ + Df = 0x20, /* Device Fault */ + Dmrd = 0x20, /* DMA ready (PACKET) */ + Drdy = 0x40, /* Device Ready */ + Bsy = 0x80, /* Busy */ +}; + +enum { /* Command */ + Cnop = 0x00, /* NOP */ + Cdr = 0x08, /* Device Reset */ + Crs = 0x20, /* Read Sectors */ + Crs48 = 0x24, /* Read Sectors Ext */ + Crd48 = 0x25, /* Read w/ DMA Ext */ + Crdq48 = 0x26, /* Read w/ DMA Queued Ext */ + Crsm48 = 0x29, /* Read Multiple Ext */ + Cws = 0x30, /* Write Sectors */ + Cws48 = 0x34, /* Write Sectors Ext */ + Cwd48 = 0x35, /* Write w/ DMA Ext */ + Cwdq48 = 0x36, /* Write w/ DMA Queued Ext */ + Cwsm48 = 0x39, /* Write Multiple Ext */ + Cedd = 0x90, /* Execute Device Diagnostics */ + Cpkt = 0xA0, /* Packet */ + Cidpkt = 0xA1, /* Identify Packet Device */ + Crsm = 0xC4, /* Read Multiple */ + Cwsm = 0xC5, /* Write Multiple */ + Csm = 0xC6, /* Set Multiple */ + Crdq = 0xC7, /* Read DMA queued */ + Crd = 0xC8, /* Read DMA */ + Cwd = 0xCA, /* Write DMA */ + Cwdq = 0xCC, /* Write DMA queued */ + Cstandby = 0xE2, /* Standby */ + Cid = 0xEC, /* Identify Device */ + Csf = 0xEF, /* Set Features */ +}; + +enum { /* Device Control */ + Nien = 0x02, /* (not) Interrupt Enable */ + Srst = 0x04, /* Software Reset */ + Hob = 0x80, /* High Order Bit [sic] */ +}; + +enum { /* PCI Configuration Registers */ + Bmiba = 0x20, /* Bus Master Interface Base Address */ + Idetim = 0x40, /* IE Timing */ + Sidetim = 0x44, /* Slave IE Timing */ + Udmactl = 0x48, /* Ultra DMA/33 Control */ + Udmatim = 0x4A, /* Ultra DMA/33 Timing */ +}; + +enum { /* Bus Master IDE I/O Ports */ + Bmicx = 0, /* Command */ + Bmisx = 2, /* Status */ + Bmidtpx = 4, /* Descriptor Table Pointer */ +}; + +enum { /* Bmicx */ + Ssbm = 0x01, /* Start/Stop Bus Master */ + Rwcon = 0x08, /* Read/Write Control */ +}; + +enum { /* Bmisx */ + Bmidea = 0x01, /* Bus Master IDE Active */ + Idedmae = 0x02, /* IDE DMA Error (R/WC) */ + Ideints = 0x04, /* IDE Interrupt Status (R/WC) */ + Dma0cap = 0x20, /* Drive 0 DMA Capable */ + Dma1cap = 0x40, /* Drive 0 DMA Capable */ +}; +enum { /* Physical Region Descriptor */ + PrdEOT = 0x80000000, /* End of Transfer */ +}; + +enum { /* offsets into the identify info. */ + Iconfig = 0, /* general configuration */ + Ilcyl = 1, /* logical cylinders */ + Ilhead = 3, /* logical heads */ + Ilsec = 6, /* logical sectors per logical track */ + Iserial = 10, /* serial number */ + Ifirmware = 23, /* firmware revision */ + Imodel = 27, /* model number */ + Imaxrwm = 47, /* max. read/write multiple sectors */ + Icapabilities = 49, /* capabilities */ + Istandby = 50, /* device specific standby timer */ + Ipiomode = 51, /* PIO data transfer mode number */ + Ivalid = 53, + Iccyl = 54, /* cylinders if (valid&0x01) */ + Ichead = 55, /* heads if (valid&0x01) */ + Icsec = 56, /* sectors if (valid&0x01) */ + Iccap = 57, /* capacity if (valid&0x01) */ + Irwm = 59, /* read/write multiple */ + Ilba = 60, /* LBA size */ + Imwdma = 63, /* multiword DMA mode */ + Iapiomode = 64, /* advanced PIO modes supported */ + Iminmwdma = 65, /* min. multiword DMA cycle time */ + Irecmwdma = 66, /* rec. multiword DMA cycle time */ + Iminpio = 67, /* min. PIO cycle w/o flow control */ + Iminiordy = 68, /* min. PIO cycle with IORDY */ + Ipcktbr = 71, /* time from PACKET to bus release */ + Iserbsy = 72, /* time from SERVICE to !Bsy */ + Iqdepth = 75, /* max. queue depth */ + Imajor = 80, /* major version number */ + Iminor = 81, /* minor version number */ + Icsfs = 82, /* command set/feature supported */ + Icsfe = 85, /* command set/feature enabled */ + Iudma = 88, /* ultra DMA mode */ + Ierase = 89, /* time for security erase */ + Ieerase = 90, /* time for enhanced security erase */ + Ipower = 91, /* current advanced power management */ + Ilba48 = 100, /* 48-bit LBA size (64 bits in 100-103) */ + Irmsn = 127, /* removable status notification */ + Isecstat = 128, /* security status */ + Icfapwr = 160, /* CFA power mode */ + Imediaserial = 176, /* current media serial number */ + Icksum = 255, /* checksum */ +}; + +enum { /* bit masks for config identify info */ + Mpktsz = 0x0003, /* packet command size */ + Mincomplete = 0x0004, /* incomplete information */ + Mdrq = 0x0060, /* DRQ type */ + Mrmdev = 0x0080, /* device is removable */ + Mtype = 0x1F00, /* device type */ + Mproto = 0x8000, /* command protocol */ +}; + +enum { /* bit masks for capabilities identify info */ + Mdma = 0x0100, /* DMA supported */ + Mlba = 0x0200, /* LBA supported */ + Mnoiordy = 0x0400, /* IORDY may be disabled */ + Miordy = 0x0800, /* IORDY supported */ + Msoftrst = 0x1000, /* needs soft reset when Bsy */ + Mstdby = 0x2000, /* standby supported */ + Mqueueing = 0x4000, /* queueing overlap supported */ + Midma = 0x8000, /* interleaved DMA supported */ +}; + +enum { /* bit masks for supported/enabled features */ + Msmart = 0x0001, + Msecurity = 0x0002, + Mrmmedia = 0x0004, + Mpwrmgmt = 0x0008, + Mpkt = 0x0010, + Mwcache = 0x0020, + Mlookahead = 0x0040, + Mrelirq = 0x0080, + Msvcirq = 0x0100, + Mreset = 0x0200, + Mprotected = 0x0400, + Mwbuf = 0x1000, + Mrbuf = 0x2000, + Mnop = 0x4000, + Mmicrocode = 0x0001, + Mqueued = 0x0002, + Mcfa = 0x0004, + Mapm = 0x0008, + Mnotify = 0x0010, + Mstandby = 0x0020, + Mspinup = 0x0040, + Mmaxsec = 0x0100, + Mautoacoustic = 0x0200, + Maddr48 = 0x0400, + Mdevconfov = 0x0800, + Mflush = 0x1000, + Mflush48 = 0x2000, + Msmarterror = 0x0001, + Msmartselftest = 0x0002, + Mmserial = 0x0004, + Mmpassthru = 0x0008, + Mlogging = 0x0020, +}; + +typedef struct Ctlr Ctlr; +typedef struct Drive Drive; + +typedef struct Prd { /* Physical Region Descriptor */ + ulong pa; /* Physical Base Address */ + int count; +} Prd; + +enum { + BMspan = 64*1024, /* must be power of 2 <= 64*1024 */ + + Nprd = SDmaxio/BMspan+2, +}; + +typedef struct Ctlr { + int cmdport; + int ctlport; + int irq; + int subirq; + int tbdf; + int bmiba; /* bus master interface base address */ + int maxio; /* sector count transfer maximum */ + int span; /* don't span this boundary with dma */ + + Pcidev* pcidev; + void (*ienable)(Ctlr*); + void (*idisable)(Ctlr*); + SDev* sdev; + + Drive* drive[2]; + + Prd* prdt; /* physical region descriptor table */ + void (*irqack)(Ctlr*); /* call to extinguish ICH intrs */ + + QLock; /* current command */ + Drive* curdrive; + int command; /* last command issued (debugging) */ + Rendez; + int done; + + /* interrupt counts */ + ulong intnil; /* no drive */ + ulong intbusy; /* controller still busy */ + ulong intok; /* normal */ + + Lock; /* register access */ +} Ctlr; + +typedef struct Drive { + Ctlr* ctlr; + + int dev; + ushort info[256]; + int c; /* cylinder */ + int h; /* head */ + int s; /* sector */ + vlong sectors; /* total */ + int secsize; /* sector size */ + + int dma; /* DMA R/W possible */ + int dmactl; + int rwm; /* read/write multiple possible */ + int rwmctl; + + int pkt; /* PACKET device, length of pktcmd */ + uchar pktcmd[16]; + int pktdma; /* this PACKET command using dma */ + + uchar sense[18]; + uchar inquiry[48]; + + QLock; /* drive access */ + int command; /* current command */ + int write; + uchar* data; + int dlen; + uchar* limit; + int count; /* sectors */ + int block; /* R/W bytes per block */ + int status; + int error; + int flags; /* internal flags */ + + /* interrupt counts */ + ulong intcmd; /* commands */ + ulong intrd; /* reads */ + ulong intwr; /* writes */ +} Drive; + +enum { /* internal flags */ + Lba48 = 0x1, /* LBA48 mode */ + Lba48always = 0x2, /* ... */ +}; +enum { + Last28 = (1<<28) - 1 - 1, /* all-ones mask is not addressible */ +}; + +static void +atadumpstate(Drive* drive, uchar* cmd, vlong lba, int count) +{ + Prd *prd; + Pcidev *p; + Ctlr *ctlr; + int i, bmiba; + + if(!(DEBUG & DbgSTATE)){ + USED(drive, cmd, lba, count); + return; + } + + ctlr = drive->ctlr; + print("sdata: command %2.2uX\n", ctlr->command); + print("data %8.8p limit %8.8p dlen %d status %uX error %uX\n", + drive->data, drive->limit, drive->dlen, + drive->status, drive->error); + if(cmd != nil){ + print("lba %d -> %lld, count %d -> %d (%d)\n", + (cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5], lba, + (cmd[7]<<8)|cmd[8], count, drive->count); + } + if(!(inb(ctlr->ctlport+As) & Bsy)){ + for(i = 1; i < 7; i++) + print(" 0x%2.2uX", inb(ctlr->cmdport+i)); + print(" 0x%2.2uX\n", inb(ctlr->ctlport+As)); + } + if(drive->command == Cwd || drive->command == Crd){ + bmiba = ctlr->bmiba; + prd = ctlr->prdt; + print("bmicx %2.2uX bmisx %2.2uX prdt %8.8p\n", + inb(bmiba+Bmicx), inb(bmiba+Bmisx), prd); + for(;;){ + print("pa 0x%8.8luX count %8.8uX\n", + prd->pa, prd->count); + if(prd->count & PrdEOT) + break; + prd++; + } + } + if(ctlr->pcidev && ctlr->pcidev->vid == 0x8086){ + p = ctlr->pcidev; + print("0x40: %4.4uX 0x42: %4.4uX", + pcicfgr16(p, 0x40), pcicfgr16(p, 0x42)); + print("0x48: %2.2uX\n", pcicfgr8(p, 0x48)); + print("0x4A: %4.4uX\n", pcicfgr16(p, 0x4A)); + } +} + +static int +atadebug(int cmdport, int ctlport, char* fmt, ...) +{ + int i, n; + va_list arg; + char buf[PRINTSIZE]; + + if(!(DEBUG & DbgPROBE)){ + USED(cmdport, ctlport, fmt); + return 0; + } + + va_start(arg, fmt); + n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf; + va_end(arg); + + if(cmdport){ + if(buf[n-1] == '\n') + n--; + n += snprint(buf+n, PRINTSIZE-n, " ataregs 0x%uX:", + cmdport); + for(i = Features; i < Command; i++) + n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX", + inb(cmdport+i)); + if(ctlport) + n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX", + inb(ctlport+As)); + n += snprint(buf+n, PRINTSIZE-n, "\n"); + } + putstrn(buf, n); + + return n; +} + +static int +ataready(int cmdport, int ctlport, int dev, int reset, int ready, int micro) +{ + int as; + + atadebug(cmdport, ctlport, "ataready: dev %uX reset %uX ready %uX", + dev, reset, ready); + + for(;;){ + /* + * Wait for the controller to become not busy and + * possibly for a status bit to become true (usually + * Drdy). Must change to the appropriate device + * register set if necessary before testing for ready. + * Always run through the loop at least once so it + * can be used as a test for !Bsy. + */ + as = inb(ctlport+As); + if(as & reset){ + /* nothing to do */ + } + else if(dev){ + outb(cmdport+Dh, dev); + dev = 0; + } + else if(ready == 0 || (as & ready)){ + atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as); + return as; + } + + if(micro-- <= 0){ + atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as); + break; + } + microdelay(1); + } + atadebug(cmdport, ctlport, "ataready: timeout"); + + return -1; +} + +static int +atadone(void* arg) +{ + return ((Ctlr*)arg)->done; +} + +static int +atarwmmode(Drive* drive, int cmdport, int ctlport, int dev) +{ + int as, maxrwm, rwm; + uchar dummy; + + maxrwm = (drive->info[Imaxrwm] & 0xFF); + if(maxrwm == 0) + return 0; + + /* + * Sometimes drives come up with the current count set + * to 0; if so, set a suitable value, otherwise believe + * the value in Irwm if the 0x100 bit is set. + */ + if(drive->info[Irwm] & 0x100) + rwm = (drive->info[Irwm] & 0xFF); + else + rwm = 0; + if(rwm == 0) + rwm = maxrwm; + if(rwm > 16) + rwm = 16; + if(ataready(cmdport, ctlport, dev, Bsy|Drq, Drdy, 102*1000) < 0) + return 0; + outb(cmdport+Count, rwm); + outb(cmdport+Command, Csm); + microdelay(1); + as = ataready(cmdport, ctlport, 0, Bsy, Drdy|Df|Err, 1000); + dummy = inb(cmdport+Status); + USED(dummy); + if(as < 0 || (as & (Df|Err))) + return 0; + + drive->rwm = rwm; + + return rwm; +} + +static int +atadmamode(Drive* drive) +{ + int dma; + + /* + * Check if any DMA mode enabled. + * Assumes the BIOS has picked and enabled the best. + * This is completely passive at the moment, no attempt is + * made to ensure the hardware is correctly set up. + */ + dma = drive->info[Imwdma] & 0x0707; + drive->dma = (dma>>8) & dma; + if(drive->dma == 0 && (drive->info[Ivalid] & 0x04)){ + dma = drive->info[Iudma] & 0x7F7F; + drive->dma = (dma>>8) & dma; + if(drive->dma) + drive->dma |= 'U'<<16; + } + + return dma; +} + +static int +ataidentify(int cmdport, int ctlport, int dev, int pkt, void* info) +{ + int as, command, drdy; + uchar dummy; + + if(pkt){ + command = Cidpkt; + drdy = 0; + } + else{ + command = Cid; + drdy = Drdy; + } + as = ataready(cmdport, ctlport, dev, Bsy|Drq, drdy, 103*1000); + if(as < 0) + return as; + outb(cmdport+Command, command); + microdelay(1); + + as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 400*1000); + if(as < 0) + return -1; + if(as & Err) + return as; + + memset(info, 0, 512); + inss(cmdport+Data, info, 256); + dummy = inb(cmdport+Status); + USED(dummy); + + if(DEBUG & DbgIDENTIFY){ + int i; + ushort *sp; + + sp = (ushort*)info; + for(i = 0; i < 256; i++){ + if(i && (i%16) == 0) + print("\n"); + print(" %4.4uX", *sp); + sp++; + } + print("\n"); + } + + return 0; +} + +static Drive* +atadrive(int cmdport, int ctlport, int dev) +{ + Drive *drive; + int as, i, pkt; + uchar buf[512], *p; + ushort iconfig, *sp; + + atadebug(0, 0, "identify: port 0x%uX dev 0x%2.2uX\n", cmdport, dev); + pkt = 1; +retry: + as = ataidentify(cmdport, ctlport, dev, pkt, buf); + if(as < 0) + return nil; + if(as & Err){ + if(pkt == 0) + return nil; + pkt = 0; + goto retry; + } + + if((drive = malloc(sizeof(Drive))) == nil) + return nil; + drive->dev = dev; + memmove(drive->info, buf, sizeof(drive->info)); + drive->sense[0] = 0x70; + drive->sense[7] = sizeof(drive->sense)-7; + + drive->inquiry[2] = 2; + drive->inquiry[3] = 2; + drive->inquiry[4] = sizeof(drive->inquiry)-4; + p = &drive->inquiry[8]; + sp = &drive->info[Imodel]; + for(i = 0; i < 20; i++){ + *p++ = *sp>>8; + *p++ = *sp++; + } + + drive->secsize = 512; + + /* + * Beware the CompactFlash Association feature set. + * Now, why this value in Iconfig just walks all over the bit + * definitions used in the other parts of the ATA/ATAPI standards + * is a mystery and a sign of true stupidity on someone's part. + * Anyway, the standard says if this value is 0x848A then it's + * CompactFlash and it's NOT a packet device. + */ + iconfig = drive->info[Iconfig]; + if(iconfig != 0x848A && (iconfig & 0xC000) == 0x8000){ + if(iconfig & 0x01) + drive->pkt = 16; + else + drive->pkt = 12; + } + else{ + if(drive->info[Ivalid] & 0x0001){ + drive->c = drive->info[Iccyl]; + drive->h = drive->info[Ichead]; + drive->s = drive->info[Icsec]; + } + else{ + drive->c = drive->info[Ilcyl]; + drive->h = drive->info[Ilhead]; + drive->s = drive->info[Ilsec]; + } + if(drive->info[Icapabilities] & Mlba){ + if(drive->info[Icsfs+1] & Maddr48){ + drive->sectors = drive->info[Ilba48] + | (drive->info[Ilba48+1]<<16) + | ((vlong)drive->info[Ilba48+2]<<32); + drive->flags |= Lba48; + } + else{ + drive->sectors = (drive->info[Ilba+1]<<16) + |drive->info[Ilba]; + } + drive->dev |= Lba; + } + else + drive->sectors = drive->c*drive->h*drive->s; + atarwmmode(drive, cmdport, ctlport, dev); + } + atadmamode(drive); + + if(DEBUG & DbgCONFIG){ + print("dev %2.2uX port %uX config %4.4uX capabilities %4.4uX", + dev, cmdport, iconfig, drive->info[Icapabilities]); + print(" mwdma %4.4uX", drive->info[Imwdma]); + if(drive->info[Ivalid] & 0x04) + print(" udma %4.4uX", drive->info[Iudma]); + print(" dma %8.8uX rwm %ud", drive->dma, drive->rwm); + if(drive->flags&Lba48) + print("\tLLBA sectors %lld", drive->sectors); + print("\n"); + } + + return drive; +} + +static void +atasrst(int ctlport) +{ + /* + * Srst is a big stick and may cause problems if further + * commands are tried before the drives become ready again. + * Also, there will be problems here if overlapped commands + * are ever supported. + */ + microdelay(5); + outb(ctlport+Dc, Srst); + microdelay(5); + outb(ctlport+Dc, 0); + microdelay(2*1000); +} + +static SDev* +ataprobe(int cmdport, int ctlport, int irq, int subirq) +{ + Ctlr* ctlr; + SDev *sdev; + Drive *drive; + int dev, error, rhi, rlo; + static int nonlegacy = 'C'; + + if(cmdport == 0) { + print("ataprobe: cmdport is 0\n"); + return nil; + } + + /* + * Try to detect a floating bus. + * Bsy should be cleared. If not, see if the cylinder registers + * are read/write capable. + * If the master fails, try the slave to catch slave-only + * configurations. + * There's no need to restore the tested registers as they will + * be reset on any detected drives by the Cedd command. + * All this indicates is that there is at least one drive on the + * controller; when the non-existent drive is selected in a + * single-drive configuration the registers of the existing drive + * are often seen, only command execution fails. + */ + dev = Dev0; + if(inb(ctlport+As) & Bsy){ + outb(cmdport+Dh, dev); + microdelay(1); +trydev1: + atadebug(cmdport, ctlport, "ataprobe bsy"); + outb(cmdport+Cyllo, 0xAA); + outb(cmdport+Cylhi, 0x55); + outb(cmdport+Sector, 0xFF); + rlo = inb(cmdport+Cyllo); + rhi = inb(cmdport+Cylhi); + if(rlo != 0xAA && (rlo == 0xFF || rhi != 0x55)){ + if(dev == Dev1){ +release: + return nil; + } + dev = Dev1; + if(ataready(cmdport, ctlport, dev, Bsy, 0, 20*1000) < 0) + goto trydev1; + } + } + + /* + * Disable interrupts on any detected controllers. + */ + outb(ctlport+Dc, Nien); +tryedd1: + if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 105*1000) < 0){ + /* + * There's something there, but it didn't come up clean, + * so try hitting it with a big stick. The timing here is + * wrong but this is a last-ditch effort and it sometimes + * gets some marginal hardware back online. + */ + atasrst(ctlport); + if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 106*1000) < 0) + goto release; + } + + /* + * Can only get here if controller is not busy. + * If there are drives Bsy will be set within 400nS, + * must wait 2mS before testing Status. + * Wait for the command to complete (6 seconds max). + */ + outb(cmdport+Command, Cedd); + delay(2); + if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 6*1000*1000) < 0) + goto release; + + /* + * If bit 0 of the error register is set then the selected drive + * exists. This is enough to detect single-drive configurations. + * However, if the master exists there is no way short of executing + * a command to determine if a slave is present. + * It appears possible to get here testing Dev0 although it doesn't + * exist and the EDD won't take, so try again with Dev1. + */ + error = inb(cmdport+Error); + atadebug(cmdport, ctlport, "ataprobe: dev %uX", dev); + if((error & ~0x80) != 0x01){ + if(dev == Dev1) + goto release; + dev = Dev1; + goto tryedd1; + } + + /* + * At least one drive is known to exist, try to + * identify it. If that fails, don't bother checking + * any further. + * If the one drive found is Dev0 and the EDD command + * didn't indicate Dev1 doesn't exist, check for it. + */ + if((drive = atadrive(cmdport, ctlport, dev)) == nil) + goto release; + if((ctlr = malloc(sizeof(Ctlr))) == nil){ + free(drive); + goto release; + } + memset(ctlr, 0, sizeof(Ctlr)); + if((sdev = malloc(sizeof(SDev))) == nil){ + free(ctlr); + free(drive); + goto release; + } + memset(sdev, 0, sizeof(SDev)); + drive->ctlr = ctlr; + if(dev == Dev0){ + ctlr->drive[0] = drive; + if(!(error & 0x80)){ + /* + * Always leave Dh pointing to a valid drive, + * otherwise a subsequent call to ataready on + * this controller may try to test a bogus Status. + * Ataprobe is the only place possibly invalid + * drives should be selected. + */ + drive = atadrive(cmdport, ctlport, Dev1); + if(drive != nil){ + drive->ctlr = ctlr; + ctlr->drive[1] = drive; + } + else{ + outb(cmdport+Dh, Dev0); + microdelay(1); + } + } + } + else + ctlr->drive[1] = drive; + + ctlr->cmdport = cmdport; + ctlr->ctlport = ctlport; + ctlr->irq = irq; + ctlr->subirq = subirq; + ctlr->tbdf = BUSUNKNOWN; + ctlr->command = Cedd; /* debugging */ + + switch(cmdport){ + default: + sdev->idno = nonlegacy; + break; + case 0x1F0: + sdev->idno = 'C'; + nonlegacy = 'E'; + break; + case 0x170: + sdev->idno = 'D'; + nonlegacy = 'E'; + break; + } + sdev->ifc = &sdataifc; + sdev->ctlr = ctlr; + sdev->nunit = 2; + ctlr->sdev = sdev; + + return sdev; +} + +static void +ataclear(SDev *sdev) +{ + Ctlr* ctlr; + + ctlr = sdev->ctlr; + if (ctlr->drive[0]) + free(ctlr->drive[0]); + if (ctlr->drive[1]) + free(ctlr->drive[1]); + if (sdev->name) + free(sdev->name); + if (sdev->unitflg) + free(sdev->unitflg); + if (sdev->unit) + free(sdev->unit); + free(ctlr); + free(sdev); +} + +static char * +atastat(SDev *sdev, char *p, char *e) +{ + Ctlr *ctlr = sdev->ctlr; + + return seprint(p, e, "%s ata port %X ctl %X irq %d.%d " + "intr-ok %lud intr-busy %lud intr-nil-drive %lud\n", + sdev->name, ctlr->cmdport, ctlr->ctlport, ctlr->irq, ctlr->subirq, + ctlr->intok, ctlr->intbusy, ctlr->intnil); +} + +/* + * These are duplicated with sdsetsense, etc., in devsd.c, but + * those assume that the disk is not SCSI while in fact here + * ata drives are not SCSI but ATAPI ones kind of are. + */ +static int +atasetsense(Drive* drive, int status, int key, int asc, int ascq) +{ + drive->sense[2] = key; + drive->sense[12] = asc; + drive->sense[13] = ascq; + + return status; +} + +static int +atamodesense(Drive* drive, uchar* cmd) +{ + int len; + + /* + * Fake a vendor-specific request with page code 0, + * return the drive info. + */ + if((cmd[2] & 0x3F) != 0 && (cmd[2] & 0x3F) != 0x3F) + return atasetsense(drive, SDcheck, 0x05, 0x24, 0); + len = (cmd[7]<<8)|cmd[8]; + if(len == 0) + return SDok; + if(len < 8+sizeof(drive->info)) + return atasetsense(drive, SDcheck, 0x05, 0x1A, 0); + if(drive->data == nil || drive->dlen < len) + return atasetsense(drive, SDcheck, 0x05, 0x20, 1); + memset(drive->data, 0, 8); + drive->data[0] = sizeof(drive->info)>>8; + drive->data[1] = sizeof(drive->info); + memmove(drive->data+8, drive->info, sizeof(drive->info)); + drive->data += 8+sizeof(drive->info); + + return SDok; +} + +static int +atastandby(Drive* drive, int period) +{ + Ctlr* ctlr; + int cmdport, done; + + ctlr = drive->ctlr; + drive->command = Cstandby; + qlock(ctlr); + + cmdport = ctlr->cmdport; + ilock(ctlr); + outb(cmdport+Count, period); + outb(cmdport+Dh, drive->dev); + ctlr->done = 0; + ctlr->curdrive = drive; + ctlr->command = Cstandby; /* debugging */ + outb(cmdport+Command, Cstandby); + iunlock(ctlr); + + while(waserror()) + ; + tsleep(ctlr, atadone, ctlr, 60*1000); + poperror(); + + done = ctlr->done; + qunlock(ctlr); + + if(!done || (drive->status & Err)) + return atasetsense(drive, SDcheck, 4, 8, drive->error); + return SDok; +} + +static void +atanop(Drive* drive, int subcommand) +{ + Ctlr* ctlr; + int as, cmdport, ctlport, timeo; + + /* + * Attempt to abort a command by using NOP. + * In response, the drive is supposed to set Abrt + * in the Error register, set (Drdy|Err) in Status + * and clear Bsy when done. However, some drives + * (e.g. ATAPI Zip) just go Bsy then clear Status + * when done, hence the timeout loop only on Bsy + * and the forced setting of drive->error. + */ + ctlr = drive->ctlr; + cmdport = ctlr->cmdport; + outb(cmdport+Features, subcommand); + outb(cmdport+Dh, drive->dev); + ctlr->command = Cnop; /* debugging */ + outb(cmdport+Command, Cnop); + + microdelay(1); + ctlport = ctlr->ctlport; + for(timeo = 0; timeo < 1000; timeo++){ + as = inb(ctlport+As); + if(!(as & Bsy)) + break; + microdelay(1); + } + drive->error |= Abrt; +} + +static void +ataabort(Drive* drive, int dolock) +{ + /* + * If NOP is available (packet commands) use it otherwise + * must try a software reset. + */ + if(dolock) + ilock(drive->ctlr); + if(drive->info[Icsfs] & Mnop) + atanop(drive, 0); + else{ + atasrst(drive->ctlr->ctlport); + drive->error |= Abrt; + } + if(dolock) + iunlock(drive->ctlr); +} + +static int +atadmasetup(Drive* drive, int len) +{ + Prd *prd; + ulong pa; + Ctlr *ctlr; + int bmiba, bmisx, count, i, span; + + ctlr = drive->ctlr; + pa = PCIWADDR(drive->data); + if(pa & 0x03) + return -1; + + /* + * Sometimes drives identify themselves as being DMA capable + * although they are not on a busmastering controller. + */ + prd = ctlr->prdt; + if(prd == nil){ + drive->dmactl = 0; + print("disabling dma: not on a busmastering controller\n"); + return -1; + } + + for(i = 0; len && i < Nprd; i++){ + prd->pa = pa; + span = ROUNDUP(pa, ctlr->span); + if(span == pa) + span += ctlr->span; + count = span - pa; + if(count >= len){ + prd->count = PrdEOT|len; + break; + } + prd->count = count; + len -= count; + pa += count; + prd++; + } + if(i == Nprd) + (prd-1)->count |= PrdEOT; + dcflush(ctlr->prdt, i*sizeof(Prd)); + + bmiba = ctlr->bmiba; + outl(bmiba+Bmidtpx, PCIWADDR(ctlr->prdt)); + if(drive->write) + outb(ctlr->bmiba+Bmicx, 0); + else + outb(ctlr->bmiba+Bmicx, Rwcon); + bmisx = inb(bmiba+Bmisx); + outb(bmiba+Bmisx, bmisx|Ideints|Idedmae); + + return 0; +} + +static void +atadmastart(Ctlr* ctlr, int write) +{ + if(write) + outb(ctlr->bmiba+Bmicx, Ssbm); + else + outb(ctlr->bmiba+Bmicx, Rwcon|Ssbm); +} + +static int +atadmastop(Ctlr* ctlr) +{ + int bmiba; + + bmiba = ctlr->bmiba; + outb(bmiba+Bmicx, inb(bmiba+Bmicx) & ~Ssbm); + + return inb(bmiba+Bmisx); +} + +static void +atadmainterrupt(Drive* drive, int count) +{ + Ctlr* ctlr; + int bmiba, bmisx; + + ctlr = drive->ctlr; + bmiba = ctlr->bmiba; + bmisx = inb(bmiba+Bmisx); + switch(bmisx & (Ideints|Idedmae|Bmidea)){ + case Bmidea: + /* + * Data transfer still in progress, nothing to do + * (this should never happen). + */ + return; + + case Ideints: + case Ideints|Bmidea: + /* + * Normal termination, tidy up. + */ + drive->data += count; + break; + + default: + /* + * What's left are error conditions (memory transfer + * problem) and the device is not done but the PRD is + * exhausted. For both cases must somehow tell the + * drive to abort. + */ + ataabort(drive, 0); + break; + } + atadmastop(ctlr); + ctlr->done = 1; +} + +static void +atapktinterrupt(Drive* drive) +{ + Ctlr* ctlr; + int cmdport, len, sts; + + ctlr = drive->ctlr; + cmdport = ctlr->cmdport; + sts = inb(cmdport+Ir) & (/*Rel|*/ Io|Cd); + /* a default case is impossible since all cases are enumerated */ + switch(sts){ + case Cd: /* write cmd */ + outss(cmdport+Data, drive->pktcmd, drive->pkt/2); + break; + + case 0: /* write data */ + len = (inb(cmdport+Bytehi)<<8)|inb(cmdport+Bytelo); + if(drive->data+len > drive->limit){ + atanop(drive, 0); + break; + } + outss(cmdport+Data, drive->data, len/2); + drive->data += len; + break; + + case Io: /* read data */ + len = (inb(cmdport+Bytehi)<<8)|inb(cmdport+Bytelo); + if(drive->data+len > drive->limit){ + atanop(drive, 0); + break; + } + inss(cmdport+Data, drive->data, len/2); + drive->data += len; + break; + + case Io|Cd: /* read cmd */ + if(drive->pktdma) + atadmainterrupt(drive, drive->dlen); + else + ctlr->done = 1; + break; + } + if(sts & Cd) + drive->intcmd++; + if(sts & Io) + drive->intrd++; + else + drive->intwr++; +} + +static int +atapktio(Drive* drive, uchar* cmd, int clen) +{ + Ctlr *ctlr; + int as, cmdport, ctlport, len, r, timeo; + + if(cmd[0] == 0x5A && (cmd[2] & 0x3F) == 0) + return atamodesense(drive, cmd); + + r = SDok; + + drive->command = Cpkt; + memmove(drive->pktcmd, cmd, clen); + memset(drive->pktcmd+clen, 0, drive->pkt-clen); + drive->limit = drive->data+drive->dlen; + + ctlr = drive->ctlr; + cmdport = ctlr->cmdport; + ctlport = ctlr->ctlport; + + qlock(ctlr); + + as = ataready(cmdport, ctlport, drive->dev, Bsy|Drq, Drdy, 107*1000); + /* used to test as&Chk as failure too, but some CD readers use that for media change */ + if(as < 0){ + qunlock(ctlr); + return -1; + } + + ilock(ctlr); + if(drive->dlen && drive->dmactl && !atadmasetup(drive, drive->dlen)) + drive->pktdma = Dma; + else + drive->pktdma = 0; + + outb(cmdport+Features, drive->pktdma); + outb(cmdport+Count, 0); + outb(cmdport+Sector, 0); + len = 16*drive->secsize; + outb(cmdport+Bytelo, len); + outb(cmdport+Bytehi, len>>8); + outb(cmdport+Dh, drive->dev); + ctlr->done = 0; + ctlr->curdrive = drive; + ctlr->command = Cpkt; /* debugging */ + if(drive->pktdma) + atadmastart(ctlr, drive->write); + outb(cmdport+Command, Cpkt); + + if((drive->info[Iconfig] & Mdrq) != 0x0020){ + microdelay(1); + as = ataready(cmdport, ctlport, 0, Bsy, Drq|Chk, 4*1000); + if(as < 0 || (as & (Bsy|Chk))){ + drive->status = as<0 ? 0 : as; + ctlr->curdrive = nil; + ctlr->done = 1; + r = SDtimeout; + }else + atapktinterrupt(drive); + } + iunlock(ctlr); + + while(waserror()) + ; + if(!drive->pktdma) + sleep(ctlr, atadone, ctlr); + else for(timeo = 0; !ctlr->done; timeo++){ + tsleep(ctlr, atadone, ctlr, 1000); + if(ctlr->done) + break; + ilock(ctlr); + atadmainterrupt(drive, 0); + if(!drive->error && timeo > 20){ + ataabort(drive, 0); + atadmastop(ctlr); + drive->dmactl = 0; + drive->error |= Abrt; + } + if(drive->error){ + drive->status |= Chk; + ctlr->curdrive = nil; + } + iunlock(ctlr); + } + poperror(); + + qunlock(ctlr); + + if(drive->status & Chk) + r = SDcheck; + + return r; +} + +static uchar cmd48[256] = { + [Crs] Crs48, + [Crd] Crd48, + [Crdq] Crdq48, + [Crsm] Crsm48, + [Cws] Cws48, + [Cwd] Cwd48, + [Cwdq] Cwdq48, + [Cwsm] Cwsm48, +}; + +static int +atageniostart(Drive* drive, uvlong lba) +{ + Ctlr *ctlr; + uchar cmd; + int as, c, cmdport, ctlport, h, len, s, use48; + + use48 = 0; + if((drive->flags&Lba48always) || lba > Last28 || drive->count > 256){ + if(!(drive->flags & Lba48)) + return -1; + use48 = 1; + c = h = s = 0; + } + else if(drive->dev & Lba){ + c = (lba>>8) & 0xFFFF; + h = (lba>>24) & 0x0F; + s = lba & 0xFF; + } + else{ + c = lba/(drive->s*drive->h); + h = ((lba/drive->s) % drive->h); + s = (lba % drive->s) + 1; + } + + ctlr = drive->ctlr; + cmdport = ctlr->cmdport; + ctlport = ctlr->ctlport; + if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, Drdy, 101*1000) < 0) + return -1; + + ilock(ctlr); + if(drive->dmactl && !atadmasetup(drive, drive->count*drive->secsize)){ + if(drive->write) + drive->command = Cwd; + else + drive->command = Crd; + } + else if(drive->rwmctl){ + drive->block = drive->rwm*drive->secsize; + if(drive->write) + drive->command = Cwsm; + else + drive->command = Crsm; + } + else{ + drive->block = drive->secsize; + if(drive->write) + drive->command = Cws; + else + drive->command = Crs; + } + drive->limit = drive->data + drive->count*drive->secsize; + cmd = drive->command; + if(use48){ + outb(cmdport+Count, drive->count>>8); + outb(cmdport+Count, drive->count); + outb(cmdport+Lbalo, lba>>24); + outb(cmdport+Lbalo, lba); + outb(cmdport+Lbamid, lba>>32); + outb(cmdport+Lbamid, lba>>8); + outb(cmdport+Lbahi, lba>>40); + outb(cmdport+Lbahi, lba>>16); + outb(cmdport+Dh, drive->dev|Lba); + cmd = cmd48[cmd]; + + if(DEBUG & Dbg48BIT) + print("using 48-bit commands\n"); + } + else{ + outb(cmdport+Count, drive->count); + outb(cmdport+Sector, s); + outb(cmdport+Cyllo, c); + outb(cmdport+Cylhi, c>>8); + outb(cmdport+Dh, drive->dev|h); + } + ctlr->done = 0; + ctlr->curdrive = drive; + ctlr->command = drive->command; /* debugging */ + outb(cmdport+Command, cmd); + + switch(drive->command){ + case Cws: + case Cwsm: + microdelay(1); + /* 10*1000 for flash ide drives - maybe detect them? */ + as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 10*1000); + if(as < 0 || (as & Err)){ + iunlock(ctlr); + return -1; + } + len = drive->block; + if(drive->data+len > drive->limit) + len = drive->limit-drive->data; + outss(cmdport+Data, drive->data, len/2); + break; + + case Crd: + dcinvalid(drive->data, drive->count*drive->secsize); + atadmastart(ctlr, drive->write); + break; + + case Cwd: + dcflush(drive->data, drive->count*drive->secsize); + atadmastart(ctlr, drive->write); + break; + } + iunlock(ctlr); + + return 0; +} + +static int +atagenioretry(Drive* drive) +{ + if(drive->dmactl){ + drive->dmactl = 0; + print("atagenioretry: disabling dma\n"); + } + else if(drive->rwmctl) + drive->rwmctl = 0; + else + return atasetsense(drive, SDcheck, 4, 8, drive->error); + + return SDretry; +} + +static int +atagenio(Drive* drive, uchar* cmd, int clen) +{ + uchar *p; + Ctlr *ctlr; + vlong lba, len; + int count, maxio; + + /* + * Map SCSI commands into ATA commands for discs. + * Fail any command with a LUN except INQUIRY which + * will return 'logical unit not supported'. + */ + if((cmd[1]>>5) && cmd[0] != 0x12) + return atasetsense(drive, SDcheck, 0x05, 0x25, 0); + + switch(cmd[0]){ + default: + return atasetsense(drive, SDcheck, 0x05, 0x20, 0); + + case 0x00: /* test unit ready */ + return SDok; + + case 0x03: /* request sense */ + if(cmd[4] < sizeof(drive->sense)) + len = cmd[4]; + else + len = sizeof(drive->sense); + if(drive->data && drive->dlen >= len){ + memmove(drive->data, drive->sense, len); + drive->data += len; + } + return SDok; + + case 0x12: /* inquiry */ + if(cmd[4] < sizeof(drive->inquiry)) + len = cmd[4]; + else + len = sizeof(drive->inquiry); + if(drive->data && drive->dlen >= len){ + memmove(drive->data, drive->inquiry, len); + drive->data += len; + } + return SDok; + + case 0x1B: /* start/stop unit */ + /* + * NOP for now, can use the power management feature + * set later. + */ + return SDok; + + case 0x25: /* read capacity */ + if((cmd[1] & 0x01) || cmd[2] || cmd[3]) + return atasetsense(drive, SDcheck, 0x05, 0x24, 0); + if(drive->data == nil || drive->dlen < 8) + return atasetsense(drive, SDcheck, 0x05, 0x20, 1); + /* + * Read capacity returns the LBA of the last sector. + */ + len = drive->sectors-1; + p = drive->data; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p++ = len; + len = drive->secsize; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p = len; + drive->data += 8; + return SDok; + + case 0x9E: /* long read capacity */ + if((cmd[1] & 0x01) || cmd[2] || cmd[3]) + return atasetsense(drive, SDcheck, 0x05, 0x24, 0); + if(drive->data == nil || drive->dlen < 8) + return atasetsense(drive, SDcheck, 0x05, 0x20, 1); + /* + * Read capacity returns the LBA of the last sector. + */ + len = drive->sectors-1; + p = drive->data; + *p++ = len>>56; + *p++ = len>>48; + *p++ = len>>40; + *p++ = len>>32; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p++ = len; + len = drive->secsize; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p = len; + drive->data += 12; + return SDok; + + case 0x28: /* read (10) */ + case 0x88: /* long read (16) */ + case 0x2a: /* write (10) */ + case 0x8a: /* long write (16) */ + case 0x2e: /* write and verify (10) */ + break; + + case 0x5A: + return atamodesense(drive, cmd); + } + + ctlr = drive->ctlr; + if(clen == 16){ + /* ata commands only go to 48-bit lba */ + if(cmd[2] || cmd[3]) + return atasetsense(drive, SDcheck, 3, 0xc, 2); + lba = (uvlong)cmd[4]<<40 | (uvlong)cmd[5]<<32; + lba |= cmd[6]<<24 | cmd[7]<<16 | cmd[8]<<8 | cmd[9]; + count = cmd[10]<<24 | cmd[11]<<16 | cmd[12]<<8 | cmd[13]; + }else{ + lba = cmd[2]<<24 | cmd[3]<<16 | cmd[4]<<8 | cmd[5]; + count = cmd[7]<<8 | cmd[8]; + } + if(drive->data == nil) + return SDok; + if(drive->dlen < count*drive->secsize) + count = drive->dlen/drive->secsize; + qlock(ctlr); + if(ctlr->maxio) + maxio = ctlr->maxio; + else if(drive->flags & Lba48) + maxio = 65536; + else + maxio = 256; + while(count){ + if(count > maxio) + drive->count = maxio; + else + drive->count = count; + if(atageniostart(drive, lba)){ + ilock(ctlr); + atanop(drive, 0); + iunlock(ctlr); + qunlock(ctlr); + return atagenioretry(drive); + } + + while(waserror()) + ; + tsleep(ctlr, atadone, ctlr, 60*1000); + poperror(); + if(!ctlr->done){ + /* + * What should the above timeout be? In + * standby and sleep modes it could take as + * long as 30 seconds for a drive to respond. + * Very hard to get out of this cleanly. + */ + atadumpstate(drive, cmd, lba, count); + ataabort(drive, 1); + qunlock(ctlr); + return atagenioretry(drive); + } + + if(drive->status & Err){ + qunlock(ctlr); + return atasetsense(drive, SDcheck, 4, 8, drive->error); + } + count -= drive->count; + lba += drive->count; + } + qunlock(ctlr); + + return SDok; +} + +static int +atario(SDreq* r) +{ + Ctlr *ctlr; + Drive *drive; + SDunit *unit; + uchar cmd10[10], *cmdp, *p; + int clen, reqstatus, status; + + unit = r->unit; + if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil){ + r->status = SDtimeout; + return SDtimeout; + } + drive = ctlr->drive[unit->subno]; + + /* + * Most SCSI commands can be passed unchanged except for + * the padding on the end. The few which require munging + * are not used internally. Mode select/sense(6) could be + * converted to the 10-byte form but it's not worth the + * effort. Read/write(6) are easy. + */ + switch(r->cmd[0]){ + case 0x08: /* read */ + case 0x0A: /* write */ + cmdp = cmd10; + memset(cmdp, 0, sizeof(cmd10)); + cmdp[0] = r->cmd[0]|0x20; + cmdp[1] = r->cmd[1] & 0xE0; + cmdp[5] = r->cmd[3]; + cmdp[4] = r->cmd[2]; + cmdp[3] = r->cmd[1] & 0x0F; + cmdp[8] = r->cmd[4]; + clen = sizeof(cmd10); + break; + + default: + cmdp = r->cmd; + clen = r->clen; + break; + } + + qlock(drive); +retry: + drive->write = r->write; + drive->data = r->data; + drive->dlen = r->dlen; + + drive->status = 0; + drive->error = 0; + if(drive->pkt) + status = atapktio(drive, cmdp, clen); + else + status = atagenio(drive, cmdp, clen); + if(status == SDretry){ + if(DbgDEBUG) + print("%s: retry: dma %8.8uX rwm %4.4uX\n", + unit->name, drive->dmactl, drive->rwmctl); + goto retry; + } + if(status == SDok){ + atasetsense(drive, SDok, 0, 0, 0); + if(drive->data){ + p = r->data; + r->rlen = drive->data - p; + } + else + r->rlen = 0; + } + else if(status == SDcheck && !(r->flags & SDnosense)){ + drive->write = 0; + memset(cmd10, 0, sizeof(cmd10)); + cmd10[0] = 0x03; + cmd10[1] = r->lun<<5; + cmd10[4] = sizeof(r->sense)-1; + drive->data = r->sense; + drive->dlen = sizeof(r->sense)-1; + drive->status = 0; + drive->error = 0; + if(drive->pkt) + reqstatus = atapktio(drive, cmd10, 6); + else + reqstatus = atagenio(drive, cmd10, 6); + if(reqstatus == SDok){ + r->flags |= SDvalidsense; + atasetsense(drive, SDok, 0, 0, 0); + } + } + qunlock(drive); + r->status = status; + if(status != SDok) + return status; + + /* + * Fix up any results. + * Many ATAPI CD-ROMs ignore the LUN field completely and + * return valid INQUIRY data. Patch the response to indicate + * 'logical unit not supported' if the LUN is non-zero. + */ + switch(cmdp[0]){ + case 0x12: /* inquiry */ + if((p = r->data) == nil) + break; + if((cmdp[1]>>5) && (!drive->pkt || (p[0] & 0x1F) == 0x05)) + p[0] = 0x7F; + /*FALLTHROUGH*/ + default: + break; + } + + return SDok; +} + +/* interrupt ack hack for intel ich controllers */ +static void +ichirqack(Ctlr *ctlr) +{ + int bmiba; + + bmiba = ctlr->bmiba; + if(bmiba) + outb(bmiba+Bmisx, inb(bmiba+Bmisx)); +} + +static void +atainterrupt(Ureg*, void* arg) +{ + Ctlr *ctlr; + Drive *drive; + int cmdport, len, status; + + ctlr = arg; + + ilock(ctlr); + if(inb(ctlr->ctlport+As) & Bsy){ + ctlr->intbusy++; + iunlock(ctlr); + if(DEBUG & DbgBsy) + print("IBsy+"); + return; + } + cmdport = ctlr->cmdport; + status = inb(cmdport+Status); + if((drive = ctlr->curdrive) == nil){ + ctlr->intnil++; + if(ctlr->irqack != nil) + ctlr->irqack(ctlr); + iunlock(ctlr); + if((DEBUG & DbgINL) && ctlr->command != Cedd) + print("Inil%2.2uX+", ctlr->command); + return; + } + + ctlr->intok++; + + if(status & Err) + drive->error = inb(cmdport+Error); + else switch(drive->command){ + default: + drive->error = Abrt; + break; + + case Crs: + case Crsm: + drive->intrd++; + if(!(status & Drq)){ + drive->error = Abrt; + break; + } + len = drive->block; + if(drive->data+len > drive->limit) + len = drive->limit-drive->data; + inss(cmdport+Data, drive->data, len/2); + drive->data += len; + if(drive->data >= drive->limit) + ctlr->done = 1; + break; + + case Cws: + case Cwsm: + drive->intwr++; + len = drive->block; + if(drive->data+len > drive->limit) + len = drive->limit-drive->data; + drive->data += len; + if(drive->data >= drive->limit){ + ctlr->done = 1; + break; + } + if(!(status & Drq)){ + drive->error = Abrt; + break; + } + len = drive->block; + if(drive->data+len > drive->limit) + len = drive->limit-drive->data; + outss(cmdport+Data, drive->data, len/2); + break; + + case Cpkt: + atapktinterrupt(drive); + break; + + case Crd: + drive->intrd++; + /* fall through */ + case Cwd: + if (drive->command == Cwd) + drive->intwr++; + atadmainterrupt(drive, drive->count*drive->secsize); + break; + + case Cstandby: + ctlr->done = 1; + break; + } + if(ctlr->irqack != nil) + ctlr->irqack(ctlr); + iunlock(ctlr); + + if(drive->error){ + status |= Err; + ctlr->done = 1; + } + + if(ctlr->done){ + ctlr->curdrive = nil; + drive->status = status; + wakeup(ctlr); + } +} + +static SDev* +atapnp(void) +{ + Ctlr *ctlr; + Pcidev *p; + SDev *legacy[2], *sdev, *head, *tail; + int channel, ispc87415, maxio, pi, r, span; + void (*irqack)(Ctlr*); + + irqack = nil; + legacy[0] = legacy[1] = head = tail = nil; + if(sdev = ataprobe(0x1F0, 0x3F4, IL8259, IrqATA0)){ + head = tail = sdev; + legacy[0] = sdev; + } + if(sdev = ataprobe(0x170, 0x374, IL8259, IrqATA1)){ + if(head != nil) + tail->next = sdev; + else + head = sdev; + tail = sdev; + legacy[1] = sdev; + } + + p = nil; + while(p = pcimatch(p, 0, 0)){ + /* + * Look for devices with the correct class and sub-class + * code and known device and vendor ID; add native-mode + * channels to the list to be probed, save info for the + * compatibility mode channels. + * Note that the legacy devices should not be considered + * PCI devices by the interrupt controller. + * For both native and legacy, save info for busmastering + * if capable. + * Promise Ultra ATA/66 (PDC20262) appears to + * 1) give a sub-class of 'other mass storage controller' + * instead of 'IDE controller', regardless of whether it's + * the only controller or not; + * 2) put 0 in the programming interface byte (probably + * as a consequence of 1) above). + * Sub-class code 0x04 is 'RAID controller', e.g. VIA VT8237. + */ + if(p->ccrb != Pcibcstore) + continue; + if(p->ccru != Pciscide) + continue; + pi = p->ccrp; + ispc87415 = 0; + maxio = 0; + span = BMspan; + + switch((p->did<<16)|p->vid){ + default: + continue; + + case (0x0002<<16)|0x100B: /* NS PC87415 */ + /* + * Disable interrupts on both channels until + * after they are probed for drives. + * This must be called before interrupts are + * enabled because the IRQ may be shared. + */ + ispc87415 = 1; + pcicfgw32(p, 0x40, 0x00000300); + break; + case (0x1000<<16)|0x1042: /* PC-Tech RZ1000 */ + /* + * Turn off prefetch. Overkill, but cheap. + */ + r = pcicfgr32(p, 0x40); + r &= ~0x2000; + pcicfgw32(p, 0x40, r); + break; + case (0x4379<<16)|0x1002: /* ATI SB400 SATA*/ + case (0x437a<<16)|0x1002: /* ATI SB400 SATA */ + case (0x439c<<16)|0x1002: /* ATI 439c SATA*/ + case (0x3373<<16)|0x105A: /* Promise 20378 RAID */ + case (0x4D30<<16)|0x105A: /* Promise PDC202xx */ + case (0x4D38<<16)|0x105A: /* Promise PDC20262 */ + case (0x4D68<<16)|0x105A: /* Promise PDC20268 */ + case (0x4D69<<16)|0x105A: /* Promise Ultra/133 TX2 */ + case (0x3112<<16)|0x1095: /* SiI 3112 SATA/RAID */ + case (0x3149<<16)|0x1106: /* VIA VT8237 SATA/RAID */ + maxio = 15; + span = 8*1024; + /*FALLTHROUGH*/ + case (0x0680<<16)|0x1095: /* SiI 0680/680A PATA133 ATAPI/RAID */ + case (0x3114<<16)|0x1095: /* SiI 3114 SATA/RAID */ + pi = 0x85; + break; + case (0x0004<<16)|0x1103: /* HighPoint HPT366 */ + pi = 0x85; + /* + * Turn off fast interrupt prediction. + */ + if((r = pcicfgr8(p, 0x51)) & 0x80) + pcicfgw8(p, 0x51, r & ~0x80); + if((r = pcicfgr8(p, 0x55)) & 0x80) + pcicfgw8(p, 0x55, r & ~0x80); + break; + case (0x0640<<16)|0x1095: /* CMD 640B */ + /* + * Bugfix code here... + */ + break; + case (0x7441<<16)|0x1022: /* AMD 768 */ + /* + * Set: + * 0x41 prefetch, postwrite; + * 0x43 FIFO configuration 1/2 and 1/2; + * 0x44 status register read retry; + * 0x46 DMA read and end of sector flush. + */ + r = pcicfgr8(p, 0x41); + pcicfgw8(p, 0x41, r|0xF0); + r = pcicfgr8(p, 0x43); + pcicfgw8(p, 0x43, (r & 0x90)|0x2A); + r = pcicfgr8(p, 0x44); + pcicfgw8(p, 0x44, r|0x08); + r = pcicfgr8(p, 0x46); + pcicfgw8(p, 0x46, (r & 0x0C)|0xF0); + /*FALLTHROUGH*/ + case (0x7401<<16)|0x1022: /* AMD 755 Cobra */ + case (0x7409<<16)|0x1022: /* AMD 756 Viper */ + case (0x7410<<16)|0x1022: /* AMD 766 Viper Plus */ + case (0x7469<<16)|0x1022: /* AMD 3111 */ + /* + * This can probably be lumped in with the 768 above. + */ + /*FALLTHROUGH*/ + case (0x209A<<16)|0x1022: /* AMD CS5536 */ + case (0x01BC<<16)|0x10DE: /* nVidia nForce1 */ + case (0x0065<<16)|0x10DE: /* nVidia nForce2 */ + case (0x0085<<16)|0x10DE: /* nVidia nForce2 MCP */ + case (0x00E3<<16)|0x10DE: /* nVidia nForce2 250 SATA */ + case (0x00D5<<16)|0x10DE: /* nVidia nForce3 */ + case (0x00E5<<16)|0x10DE: /* nVidia nForce3 Pro */ + case (0x00EE<<16)|0x10DE: /* nVidia nForce3 250 SATA */ + case (0x0035<<16)|0x10DE: /* nVidia nForce3 MCP */ + case (0x0053<<16)|0x10DE: /* nVidia nForce4 */ + case (0x0054<<16)|0x10DE: /* nVidia nForce4 SATA */ + case (0x0055<<16)|0x10DE: /* nVidia nForce4 SATA */ + case (0x0266<<16)|0x10DE: /* nVidia nForce4 430 SATA */ + case (0x0267<<16)|0x10DE: /* nVidia nForce 55 MCP SATA */ + case (0x03EC<<16)|0x10DE: /* nVidia nForce 61 MCP SATA */ + case (0x0448<<16)|0x10DE: /* nVidia nForce 65 MCP SATA */ + case (0x0560<<16)|0x10DE: /* nVidia nForce 69 MCP SATA */ + /* + * Ditto, although it may have a different base + * address for the registers (0x50?). + */ + /*FALLTHROUGH*/ + case (0x4376<<16)|0x1002: /* ATI SB400 PATA */ + case (0x438c<<16)|0x1002: /* ATI SB600 PATA */ + break; + case (0x0211<<16)|0x1166: /* ServerWorks IB6566 */ + { + Pcidev *sb; + + sb = pcimatch(nil, 0x1166, 0x0200); + if(sb == nil) + break; + r = pcicfgr32(sb, 0x64); + r &= ~0x2000; + pcicfgw32(sb, 0x64, r); + } + span = 32*1024; + break; + case (0x0502<<17)|0x100B: /* NS SC1100/SCx200 */ + case (0x5229<<16)|0x10B9: /* ALi M1543 */ + case (0x5288<<16)|0x10B9: /* ALi M5288 SATA */ + case (0x5513<<16)|0x1039: /* SiS 962 */ + case (0x0646<<16)|0x1095: /* CMD 646 */ + case (0x0571<<16)|0x1106: /* VIA 82C686 */ + case (0x2363<<16)|0x197b: /* JMicron SATA */ + break; /* TODO: verify that this should be here; wasn't in original patch */ + case (0x1230<<16)|0x8086: /* 82371FB (PIIX) */ + case (0x7010<<16)|0x8086: /* 82371SB (PIIX3) */ + case (0x7111<<16)|0x8086: /* 82371[AE]B (PIIX4[E]) */ + case (0x2411<<16)|0x8086: /* 82801AA (ICH) */ + case (0x2421<<16)|0x8086: /* 82801AB (ICH0) */ + case (0x244A<<16)|0x8086: /* 82801BA (ICH2, Mobile) */ + case (0x244B<<16)|0x8086: /* 82801BA (ICH2, High-End) */ + case (0x248A<<16)|0x8086: /* 82801CA (ICH3, Mobile) */ + case (0x248B<<16)|0x8086: /* 82801CA (ICH3, High-End) */ + case (0x24CA<<16)|0x8086: /* 82801DBM (ICH4, Mobile) */ + case (0x24CB<<16)|0x8086: /* 82801DB (ICH4, High-End) */ + case (0x24D1<<16)|0x8086: /* 82801EB/ER (ICH5 High-End) */ + case (0x24DB<<16)|0x8086: /* 82801EB (ICH5) */ + case (0x25A3<<16)|0x8086: /* 6300ESB (E7210) */ + case (0x2653<<16)|0x8086: /* 82801FBM (ICH6M) */ + case (0x266F<<16)|0x8086: /* 82801FB (ICH6) */ + case (0x27DF<<16)|0x8086: /* 82801G SATA (ICH7) */ + case (0x27C0<<16)|0x8086: /* 82801GB SATA AHCI (ICH7) */ +// case (0x27C4<<16)|0x8086: /* 82801GBM SATA (ICH7) */ + case (0x27C5<<16)|0x8086: /* 82801GBM SATA AHCI (ICH7) */ + case (0x2920<<16)|0x8086: /* 82801(IB)/IR/IH/IO SATA IDE (ICH9) */ + case (0x3a20<<16)|0x8086: /* 82801JI (ICH10) */ + case (0x3a26<<16)|0x8086: /* 82801JI (ICH10) */ + irqack = ichirqack; + break; + } + + for(channel = 0; channel < 2; channel++){ + if(pi & (1<<(2*channel))){ + sdev = ataprobe(p->mem[0+2*channel].bar & ~0x01, + p->mem[1+2*channel].bar & ~0x01, + ILpci, pcisubirq(p->tbdf)); + if(sdev == nil) + continue; + + ctlr = sdev->ctlr; + if(ispc87415) { +// ctlr->ienable = pc87415ienable; + print("pc87415disable: not yet implemented\n"); + } + + if(head != nil) + tail->next = sdev; + else + head = sdev; + tail = sdev; + ctlr->tbdf = p->tbdf; + } + else if((sdev = legacy[channel]) == nil) + continue; + else + ctlr = sdev->ctlr; + + ctlr->pcidev = p; + ctlr->maxio = maxio; + ctlr->span = span; + ctlr->irqack = irqack; + if(!(pi & 0x80)) + continue; + ctlr->bmiba = (p->mem[4].bar & ~0x01) + channel*8; + } + } + + return head; +} + +static SDev* +atalegacy(int port, int irq) +{ + return ataprobe(port, port+0x204, IL8259, irq); +} + +static int +ataenable(SDev* sdev) +{ + Ctlr *ctlr; + char name[32]; + + ctlr = sdev->ctlr; + + if(ctlr->bmiba){ + if(ctlr->pcidev != nil) + pcisetbme(ctlr->pcidev); + ctlr->prdt = mallocalign(Nprd*sizeof(Prd), 4, 0, 4*1024); + if(ctlr->prdt == nil) + error(Enomem); + } + snprint(name, sizeof(name), "%s (%s)", sdev->name, sdev->ifc->name); + intrenable(ctlr->irq, atainterrupt, ctlr, ctlr->subirq); + outb(ctlr->ctlport+Dc, 0); + if(ctlr->ienable) + ctlr->ienable(ctlr); + + return 1; +} + +static int +atadisable(SDev *sdev) +{ + Ctlr *ctlr; + char name[32]; + + ctlr = sdev->ctlr; + outb(ctlr->ctlport+Dc, Nien); /* disable interrupts */ + if (ctlr->idisable) + ctlr->idisable(ctlr); + snprint(name, sizeof(name), "%s (%s)", sdev->name, sdev->ifc->name); +// intrdisable(ctlr->irq, atainterrupt, ctlr, ctlr->subirq); // XXX + if (ctlr->bmiba) { + if (ctlr->pcidev) + pciclrbme(ctlr->pcidev); + sdfree(ctlr->prdt); + } + return 0; +} + +static int +atarctl(SDunit* unit, char* p, int l) +{ + int n; + Ctlr *ctlr; + Drive *drive; + + if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) + return 0; + drive = ctlr->drive[unit->subno]; + + qlock(drive); + n = snprint(p, l, "config %4.4uX capabilities %4.4uX", + drive->info[Iconfig], drive->info[Icapabilities]); + if(drive->dma) + n += snprint(p+n, l-n, " dma %8.8uX dmactl %8.8uX", + drive->dma, drive->dmactl); + if(drive->rwm) + n += snprint(p+n, l-n, " rwm %ud rwmctl %ud", + drive->rwm, drive->rwmctl); + if(drive->flags&Lba48) + n += snprint(p+n, l-n, " lba48always %s", + (drive->flags&Lba48always) ? "on" : "off"); + n += snprint(p+n, l-n, "\n"); + n += snprint(p+n, l-n, "interrupts read %lud write %lud cmds %lud\n", + drive->intrd, drive->intwr, drive->intcmd); + if(drive->sectors){ + n += snprint(p+n, l-n, "geometry %lld %d", + drive->sectors, drive->secsize); + if(drive->pkt == 0) + n += snprint(p+n, l-n, " %d %d %d", + drive->c, drive->h, drive->s); + n += snprint(p+n, l-n, "\n"); + } + qunlock(drive); + + return n; +} + +static int +atawctl(SDunit* unit, Cmdbuf* cb) +{ + int period; + Ctlr *ctlr; + Drive *drive; + + if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) + return 0; + drive = ctlr->drive[unit->subno]; + + qlock(drive); + if(waserror()){ + qunlock(drive); + nexterror(); + } + + /* + * Dma and rwm control is passive at the moment, + * i.e. it is assumed that the hardware is set up + * correctly already either by the BIOS or when + * the drive was initially identified. + */ + if(strcmp(cb->f[0], "dma") == 0){ + if(cb->nf != 2 || drive->dma == 0) + error(Ebadctl); + if(strcmp(cb->f[1], "on") == 0) + drive->dmactl = drive->dma; + else if(strcmp(cb->f[1], "off") == 0) + drive->dmactl = 0; + else + error(Ebadctl); + } + else if(strcmp(cb->f[0], "rwm") == 0){ + if(cb->nf != 2 || drive->rwm == 0) + error(Ebadctl); + if(strcmp(cb->f[1], "on") == 0) + drive->rwmctl = drive->rwm; + else if(strcmp(cb->f[1], "off") == 0) + drive->rwmctl = 0; + else + error(Ebadctl); + } + else if(strcmp(cb->f[0], "standby") == 0){ + switch(cb->nf){ + default: + error(Ebadctl); + case 2: + period = strtol(cb->f[1], 0, 0); + if(period && (period < 30 || period > 240*5)) + error(Ebadctl); + period /= 5; + break; + } + if(atastandby(drive, period) != SDok) + error(Ebadctl); + } + else if(strcmp(cb->f[0], "lba48always") == 0){ + if(cb->nf != 2 || !(drive->flags&Lba48)) + error(Ebadctl); + if(strcmp(cb->f[1], "on") == 0) + drive->flags |= Lba48always; + else if(strcmp(cb->f[1], "off") == 0) + drive->flags &= ~Lba48always; + else + error(Ebadctl); + } + else + error(Ebadctl); + qunlock(drive); + poperror(); + + return 0; +} + +SDifc sdataifc = { + "ata", /* name */ + + atapnp, /* pnp */ + atalegacy, /* legacy */ + ataenable, /* enable */ + atadisable, /* disable */ + + scsiverify, /* verify */ + scsionline, /* online */ + atario, /* rio */ + atarctl, /* rctl */ + atawctl, /* wctl */ + + scsibio, /* bio */ + nil, /* probe */ + ataclear, /* clear */ + atastat, /* rtopctl */ + nil, /* wtopctl */ +}; --- /dev/null +++ /sys/src/9/loongson/sdscsi.c @@ -0,0 +1,427 @@ +/* + * copy from ../port/sdscsi.c, only 2 lines changed (sdfree) + */ + +#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 "../port/sd.h" + +static int +scsitest(SDreq* r) +{ + r->write = 0; + memset(r->cmd, 0, sizeof(r->cmd)); + r->cmd[1] = r->lun<<5; + r->clen = 6; + r->data = nil; + r->dlen = 0; + r->flags = 0; + + r->status = ~0; + + return r->unit->dev->ifc->rio(r); +} + +int +scsiverify(SDunit* unit) +{ + SDreq *r; + int i, status; + uchar *inquiry; + + if((r = malloc(sizeof(SDreq))) == nil) + return 0; + if((inquiry = sdmalloc(sizeof(unit->inquiry))) == nil){ + free(r); + return 0; + } + r->unit = unit; + r->lun = 0; /* ??? */ + + memset(unit->inquiry, 0, sizeof(unit->inquiry)); + r->write = 0; + r->cmd[0] = 0x12; + r->cmd[1] = r->lun<<5; + r->cmd[4] = sizeof(unit->inquiry)-1; + r->clen = 6; + r->data = inquiry; + r->dlen = sizeof(unit->inquiry)-1; + r->flags = 0; + + r->status = ~0; + if(unit->dev->ifc->rio(r) != SDok){ + free(r); + return 0; + } + memmove(unit->inquiry, inquiry, r->dlen); + sdfree(inquiry); + + SET(status); + for(i = 0; i < 3; i++){ + while((status = scsitest(r)) == SDbusy) + ; + if(status == SDok || status != SDcheck) + break; + if(!(r->flags & SDvalidsense)) + break; + if((r->sense[2] & 0x0F) != 0x02) + continue; + + /* + * Unit is 'not ready'. + * If it is in the process of becoming ready or needs + * an initialising command, set status so it will be spun-up + * below. + * If there's no medium, that's OK too, but don't + * try to spin it up. + */ + if(r->sense[12] == 0x04){ + if(r->sense[13] == 0x02 || r->sense[13] == 0x01){ + status = SDok; + break; + } + } + if(r->sense[12] == 0x3A) + break; + } + + if(status == SDok){ + /* + * Try to ensure a direct-access device is spinning. + * Don't wait for completion, ignore the result. + */ + if((unit->inquiry[0] & SDinq0periphtype) == SDperdisk){ + memset(r->cmd, 0, sizeof(r->cmd)); + r->write = 0; + r->cmd[0] = 0x1B; + r->cmd[1] = (r->lun<<5)|0x01; + r->cmd[4] = 1; + r->clen = 6; + r->data = nil; + r->dlen = 0; + r->flags = 0; + + r->status = ~0; + unit->dev->ifc->rio(r); + } + } + free(r); + + if(status == SDok || status == SDcheck) + return 1; + return 0; +} + +static int +scsirio(SDreq* r) +{ + /* + * Perform an I/O request, returning + * -1 failure + * 0 ok + * 1 no medium present + * 2 retry + * The contents of r may be altered so the + * caller should re-initialise if necesary. + */ + r->status = ~0; + switch(r->unit->dev->ifc->rio(r)){ + default: + break; + case SDcheck: + if(!(r->flags & SDvalidsense)) + break; + switch(r->sense[2] & 0x0F){ + case 0x00: /* no sense */ + case 0x01: /* recovered error */ + return 2; + case 0x06: /* check condition */ + /* + * 0x28 - not ready to ready transition, + * medium may have changed. + * 0x29 - power on or some type of reset. + */ + if(r->sense[12] == 0x28 && r->sense[13] == 0) + return 2; + if(r->sense[12] == 0x29) + return 2; + break; + case 0x02: /* not ready */ + /* + * If no medium present, bail out. + * If unit is becoming ready, rather than not + * not ready, wait a little then poke it again. */ + if(r->sense[12] == 0x3A) + break; + if(r->sense[12] != 0x04 || r->sense[13] != 0x01) + break; + + while(waserror()) + ; + tsleep(&up->sleep, return0, 0, 500); + poperror(); + scsitest(r); + return 2; + default: + break; + } + break; + case SDok: + return 0; + } + return -1; +} + +int +scsionline(SDunit* unit) +{ + SDreq *r; + uchar *p; + int ok, retries; + + if((r = malloc(sizeof(SDreq))) == nil) + return 0; + if((p = sdmalloc(8)) == nil){ + free(r); + return 0; + } + + ok = 0; + + r->unit = unit; + r->lun = 0; /* ??? */ + for(retries = 0; retries < 10; retries++){ + /* + * Read-capacity is mandatory for DA, WORM, CD-ROM and + * MO. It may return 'not ready' if type DA is not + * spun up, type MO or type CD-ROM are not loaded or just + * plain slow getting their act together after a reset. + */ + r->write = 0; + memset(r->cmd, 0, sizeof(r->cmd)); + r->cmd[0] = 0x25; + r->cmd[1] = r->lun<<5; + r->clen = 10; + r->data = p; + r->dlen = 8; + r->flags = 0; + + r->status = ~0; + switch(scsirio(r)){ + default: + break; + case 0: + unit->sectors = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; + unit->secsize = (p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7]; + + /* + * Some ATAPI CD readers lie about the block size. + * Since we don't read audio via this interface + * it's okay to always fudge this. + */ + if(unit->secsize == 2352) + unit->secsize = 2048; + /* + * Devices with removable media may return 0 sectors + * when they have empty media (e.g. sata dvd writers); + * if so, keep the count zero. + * + * Read-capacity returns the LBA of the last sector, + * therefore the number of sectors must be incremented. + */ + if(unit->sectors != 0) + unit->sectors++; + ok = 1; + break; + case 1: + ok = 1; + break; + case 2: + continue; + } + break; + } + sdfree(p); + free(r); + + if(ok) + return ok+retries; + else + return 0; +} + +int +scsiexec(SDunit* unit, int write, uchar* cmd, int clen, void* data, int* dlen) +{ + SDreq *r; + int status; + + if((r = malloc(sizeof(SDreq))) == nil) + return SDmalloc; + r->unit = unit; + r->lun = cmd[1]>>5; /* ??? */ + r->write = write; + memmove(r->cmd, cmd, clen); + r->clen = clen; + r->data = data; + if(dlen) + r->dlen = *dlen; + r->flags = 0; + + r->status = ~0; + + /* + * Call the device-specific I/O routine. + * There should be no calls to 'error()' below this + * which percolate back up. + */ + switch(status = unit->dev->ifc->rio(r)){ + case SDok: + if(dlen) + *dlen = r->rlen; + /*FALLTHROUGH*/ + case SDcheck: + /*FALLTHROUGH*/ + default: + /* + * It's more complicated than this. There are conditions + * which are 'ok' but for which the returned status code + * is not 'SDok'. + * Also, not all conditions require a reqsense, might + * need to do a reqsense here and make it available to the + * caller somehow. + * + * Mañana. + */ + break; + } + free(r); + + return status; +} + +static void +scsifmt10(SDreq *r, int write, int lun, ulong nb, uvlong bno) +{ + uchar *c; + + c = r->cmd; + if(write == 0) + c[0] = 0x28; + else + c[0] = 0x2A; + c[1] = lun<<5; + c[2] = bno>>24; + c[3] = bno>>16; + c[4] = bno>>8; + c[5] = bno; + c[6] = 0; + c[7] = nb>>8; + c[8] = nb; + c[9] = 0; + + r->clen = 10; +} + +static void +scsifmt16(SDreq *r, int write, int lun, ulong nb, uvlong bno) +{ + uchar *c; + + c = r->cmd; + if(write == 0) + c[0] = 0x88; + else + c[0] = 0x8A; + c[1] = lun<<5; /* so wrong */ + c[2] = bno>>56; + c[3] = bno>>48; + c[4] = bno>>40; + c[5] = bno>>32; + c[6] = bno>>24; + c[7] = bno>>16; + c[8] = bno>>8; + c[9] = bno; + c[10] = nb>>24; + c[11] = nb>>16; + c[12] = nb>>8; + c[13] = nb; + c[14] = 0; + c[15] = 0; + + r->clen = 16; +} + +long +scsibio(SDunit* unit, int lun, int write, void* data, long nb, uvlong bno) +{ + SDreq *r; + long rlen; + + if((r = malloc(sizeof(SDreq))) == nil) + error(Enomem); + r->unit = unit; + r->lun = lun; +again: + r->write = write; + if(bno >= (1ULL<<32)) + scsifmt16(r, write, lun, nb, bno); + else + scsifmt10(r, write, lun, nb, bno); + r->data = data; + r->dlen = nb*unit->secsize; + r->flags = 0; + + r->status = ~0; + switch(scsirio(r)){ + default: + rlen = -1; + break; + case 0: + rlen = r->rlen; + break; + case 2: + rlen = -1; + if(!(r->flags & SDvalidsense)) + break; + switch(r->sense[2] & 0x0F){ + default: + break; + case 0x01: /* recovered error */ + print("%s: recovered error at sector %llud\n", + unit->name, bno); + rlen = r->rlen; + break; + case 0x06: /* check condition */ + /* + * Check for a removeable media change. + * If so, mark it by zapping the geometry info + * to force an online request. + */ + if(r->sense[12] != 0x28 || r->sense[13] != 0) + break; + if(unit->inquiry[1] & SDinq1removable) + unit->sectors = 0; + break; + case 0x02: /* not ready */ + /* + * If unit is becoming ready, + * rather than not not ready, try again. + */ + if(r->sense[12] == 0x04 && r->sense[13] == 0x01) + goto again; + break; + } + break; + } + free(r); + + return rlen; +} --- /dev/null +++ /sys/src/9/loongson/spim.s @@ -0,0 +1,77 @@ +#undef MASK +#define MASK(w) ((1<<(w))-1) + +#define NOP NOR R0, R0, R0 +#define CONST(x,r) MOVW $((x)&0xffff0000), r; OR $((x)&0xffff), r + +#define LL(base, rt) WORD $((060<<26)|((base)<<21)|((rt)<<16)) +#define SC(base, rt) WORD $((070<<26)|((base)<<21)|((rt)<<16)) + +#define ERET WORD $0x42000018; NOP +#define SYNC WORD $0xf /* all sync barriers */ +#define EHB NOP /* seems work */ +#define WAIT /* loongson 2e does not have this */ + +#define JALR(d, r) WORD $(((r)<<21)|((d)<<11)|9); NOP + +#define TOKSEG0(r) MOVW $ret0(SB), R(r); JALR(22, r) +#define TOKSEG1(r) MOVW $ret1(SB), R(r); OR $KSEG1, R(r); JALR(22, r) + +/* + * cache manipulation + */ + +#define CACHE BREAK /* overloaded op-code */ + +#define PI R((0 /* primary I cache */ +#define PD R((1 /* primary D cache */ +#define TD R((2 /* tertiary I/D cache */ +#define SD R((3 /* secondary combined I/D cache */ + +#define IWBI (0<<2))) /* index write-back invalidate */ +#define ILT (1<<2))) /* index load tag */ +#define IST (2<<2))) /* index store tag */ +/* #define CDE (3<<2))) /* create dirty exclusive */ +#define HINV (4<<2))) /* hit invalidate */ +#define HWBI (5<<2))) /* hit write back invalidate */ +#define ILD (6<<2))) /* index load data (loongson 2e) */ +#define ISD (7<<2))) /* index store data (loongson 2e) */ + +/* + * serial output + */ + +#define PUTC(c, r1, r2) CONST(PHYSCONS, r1); MOVW $(c), r2; MOVBU r2, (r1); NOP + +#define DELAY(r) \ + CONST(34000000, r); \ + SUBU $1, r; \ + BNE r, -1(PC) + +/* + * FCR31 bits copied from u.h + */ + +/* FCR (FCR31) */ +#define FPINEX (1<<7) /* enables */ +#define FPUNFL (1<<8) +#define FPOVFL (1<<9) +#define FPZDIV (1<<10) +#define FPINVAL (1<<11) +#define FPRNR (0<<0) /* rounding modes */ +#define FPRZ (1<<0) +#define FPRPINF (2<<0) +#define FPRNINF (3<<0) +#define FPRMASK (3<<0) +#define FPPEXT 0 +#define FPPSGL 0 +#define FPPDBL 0 +#define FPPMASK 0 +#define FPCOND (1<<23) + +/* FSR (also FCR31) */ +#define FPAINEX (1<<2) /* flags */ +#define FPAOVFL (1<<4) +#define FPAUNFL (1<<3) +#define FPAZDIV (1<<5) +#define FPAINVAL (1<<6) --- /dev/null +++ /sys/src/9/loongson/trap.c @@ -0,0 +1,1096 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "ureg.h" +#include "io.h" +#include +#include "../port/error.h" + +#define setstatus(v) /* experiment: delete this to enable recursive traps */ + +typedef struct Handler Handler; + +struct Handler { + void (*handler)(Ureg*, void*); + void *arg; + Handler *next; /* at this interrupt level */ + ulong intrs; +}; + +ulong offintrs; +ulong intrcauses[ILmax+1]; + +int intr(Ureg*); +void kernfault(Ureg*, int); +void noted(Ureg*, Ureg**, ulong); +void rfnote(Ureg**); + +char *excname[] = +{ + "trap: external interrupt", + "trap: TLB modification (store to unwritable)", + "trap: TLB miss (load or fetch)", + "trap: TLB miss (store)", + "trap: address error (load or fetch)", + "trap: address error (store)", + "trap: bus error (fetch)", + "trap: bus error (data load or store)", + "trap: system call", + "breakpoint", + "trap: reserved instruction", + "trap: coprocessor unusable", + "trap: arithmetic overflow", + "trap: TRAP exception", + "trap: VCE (instruction)", + "trap: floating-point exception", + "trap: coprocessor 2 implementation-specific", /* used as sys call for debugger */ + "trap: corextend unusable", + "trap: precise coprocessor 2 exception", + "trap: TLB read-inhibit", + "trap: TLB execute-inhibit", + "trap: undefined 21", + "trap: undefined 22", + "trap: WATCH exception", + "trap: machine checkcore", + "trap: undefined 25", + "trap: undefined 26", + "trap: undefined 27", + "trap: undefined 28", + "trap: undefined 29", + "trap: cache error", + "trap: VCE (data)", +}; + +char *fpcause[] = +{ + "inexact operation", + "underflow", + "overflow", + "division by zero", + "invalid operation", +}; + +struct { + char *name; + uint off; +} regname[] = { + "STATUS", Ureg_status, + "PC", Ureg_pc, + "SP", Ureg_sp, + "CAUSE",Ureg_cause, + "BADADDR", Ureg_badvaddr, + "TLBVIRT", Ureg_tlbvirt, + "HI", Ureg_hi, + "LO", Ureg_lo, + "R31", Ureg_r31, + "R30", Ureg_r30, + "R28", Ureg_r28, + "R27", Ureg_r27, + "R26", Ureg_r26, + "R25", Ureg_r25, + "R24", Ureg_r24, + "R23", Ureg_r23, + "R22", Ureg_r22, + "R21", Ureg_r21, + "R20", Ureg_r20, + "R19", Ureg_r19, + "R18", Ureg_r18, + "R17", Ureg_r17, + "R16", Ureg_r16, + "R15", Ureg_r15, + "R14", Ureg_r14, + "R13", Ureg_r13, + "R12", Ureg_r12, + "R11", Ureg_r11, + "R10", Ureg_r10, + "R9", Ureg_r9, + "R8", Ureg_r8, + "R7", Ureg_r7, + "R6", Ureg_r6, + "R5", Ureg_r5, + "R4", Ureg_r4, + "R3", Ureg_r3, + "R2", Ureg_r2, + "R1", Ureg_r1, +}; + +static Lock intrlock; +static Handler handlers[ILmax+1]; +static ulong pciintrmask; +static ulong i8259intrmask; + +static char * +ptlb(ulong phys) +{ + static char buf[4][32]; + static int k; + char *p; + + k = (k+1)&3; + p = buf[k]; + p += snprint(p, sizeof buf[k] - (p - buf[k]), "(%#lux %lud ", + (phys<<6) & ~(BY2PG-1), (phys>>3)&7); + if(phys & 4) + *p++ = 'd'; + if(phys & 2) + *p++ = 'v'; + if(phys & 1) + *p++ = 'g'; + *p++ = ')'; + *p = 0; + return buf[k]; +} + +static void +kpteprint(Ureg *ur) +{ + ulong i, tlbstuff[3]; + KMap *k; + + i = (ur->badvaddr & ~(2*BY2PG-1)) | TLBPID(tlbvirt()); + print("tlbvirt=%#lux\n", i); + i = gettlbp(i, tlbstuff); + print("i=%lud v=%#lux p0=%s p1=%s\n", + i, tlbstuff[0], ptlb(tlbstuff[1]), ptlb(tlbstuff[2])); + + i = (ur->badvaddr & ~KMAPADDR)>>15; + if(i > KPTESIZE){ + print("kpte index = %lud ?\n", i); + return; + } + k = &kpte[i]; + print("i=%lud, &k=%#p, k={v=%#lux, p0=%s, p1=%s, pg=%#p}\n", + i, k, k->virt, ptlb(k->phys0), ptlb(k->phys1), k->pg); + print("pg={pa=%#lux, va=%#lux}\n", k->pg->pa, k->pg->va); +} + +void +kvce(Ureg *ur, int ecode) +{ + char c; + Pte **p; + Page **pg; + Segment *s; + ulong addr, soff; + + c = 'D'; + if(ecode == CVCEI) + c = 'I'; + print("Trap: VCE%c: addr=%#lux\n", c, ur->badvaddr); + if((ur->badvaddr & KSEGM) == KSEG3) { + kpteprint(ur); + return; + } + if(up && !(ur->badvaddr & KSEGM)) { + addr = ur->badvaddr; + s = seg(up, addr, 0); + if(s == 0){ + print("kvce: no seg for %#lux\n", addr); + for(;;) + ; + } + addr &= ~(BY2PG-1); + soff = addr - s->base; + p = &s->map[soff/PTEMAPMEM]; + if(*p){ + pg = &(*p)->pages[(soff&(PTEMAPMEM-1))/BY2PG]; + if(*pg) + print("kvce: pa=%#lux, va=%#lux\n", + (*pg)->pa, (*pg)->va); + else + print("kvce: no *pg\n"); + }else + print("kvce: no *p\n"); + } +} + +/* prepare to go to user space */ +void +kexit(Ureg*) +{ + Tos *tos; + + /* precise time accounting, kernel exit */ + tos = (Tos*)(USTKTOP-sizeof(Tos)); + tos->kcycles += fastticks(&tos->cyclefreq) - up->kentry; + tos->pcycles = up->pcycles; + tos->pid = up->pid; +} + +char* +fpexcname(Ureg *ur, ulong fcr31, char *buf, uint size) +{ + int i; + char *s; + ulong fppc; + + fppc = ur->pc; + if(ur->cause & BD) /* branch delay */ + fppc += 4; + s = 0; + if(fcr31 & FPUNIMP) + s = "unimplemented operation"; + else { + fcr31 >>= 7; /* trap enable bits */ + fcr31 &= (fcr31>>5); /* anded with exceptions */ + for(i=0; i<5; i++) + if(fcr31 & (1<cause>>2)&EXCMASK; + user = ur->status&KUSER; + if (ur->cause & TS) + panic("trap: tlb shutdown"); + + fpchk = 0; + if(user){ + up->dbgreg = ur; + cycles(&up->kentry); + if(up && up->fpstate == FPactive) { + if((ur->status & CU1) == 0) + panic("FPactive but no CU1"); + ur->status &= ~CU1; + up->fpstate = FPinactive; + savefpregs(&up->fpsave); + } + } + + if (up && (char *)(ur) - up->kstack < 1024 && dumps++ == 0) { + iprint("trap: proc %ld kernel stack getting full\n", up->pid); + dumpregs(ur); + dumpstack(); + } + if (up == nil && + (char *)(ur) - (char *)m->stack < 1024 && dumps++ == 0) { + iprint("trap: cpu%d kernel stack getting full\n", m->machno); + dumpregs(ur); + dumpstack(); + } + +// splhi(); /* for the experiment: make it explicit */ + /* clear EXL in status */ + setstatus(getstatus() & ~EXL); + + clockintr = 0; + switch(ecode){ + case CINT: + clockintr = intr(ur); + break; + + case CFPE: + fptrap(ur); + clrfpintr(); + fpchk = 1; + break; + + case CTLBM: + case CTLBL: + case CTLBS: + /* user tlb entries assumed not overwritten during startup */ + if(up == 0) + kernfault(ur, ecode); + + if(!user && (ur->badvaddr & KSEGM) == KSEG3) { + kfault(ur); + break; + } + x = up->insyscall; + up->insyscall = 1; + spllo(); + faultmips(ur, user, ecode); + up->insyscall = x; + break; + + case CVCEI: + case CVCED: + kvce(ur, ecode); + goto Default; + + case CWATCH: + if(!user) + panic("watchpoint trap from kernel mode pc=%#p", + ur->pc); + //fpwatch(ur); XXX + break; + + case CCPU: + cop = (ur->cause>>28)&3; + if(user && up && cop == 1) { + if(up->fpstate & FPillegal) { + /* someone used floating point in a note handler */ + postnote(up, 1, + "sys: floating point in note handler", + NDebug); + break; + } + if(up->fpstate == FPinit) { + up->fpstate = FPinactive; + fpfcr31 = up->fpsave.fpstatus; + up->fpsave = initfp; + up->fpsave.fpstatus = fpfcr31; + break; + } + if(up->fpstate == FPinactive) + break; + } + /* Fallthrough */ + + Default: + default: + if(user) { + spllo(); + snprint(buf, sizeof buf, "sys: %s", excname[ecode]); + postnote(up, 1, buf, NDebug); + break; + } + if (ecode == CADREL || ecode == CADRES) + iprint("kernel addr exception for va %#p pid %#ld %s\n", + ur->badvaddr, (up? up->pid: 0), + (up? up->text: "")); + print("cpu%d: kernel %s pc=%#lux\n", + m->machno, excname[ecode], ur->pc); + dumpregs(ur); + dumpstack(); + if(m->machno == 0) + spllo(); + exit(1); + } + + if(fpchk) { + fpfcr31 = up->fpsave.fpstatus; + if((fpfcr31>>12) & ((fpfcr31>>7)|0x20) & 0x3f) { + spllo(); + fpexcep = fpexcname(ur, fpfcr31, buf1, sizeof buf1); + snprint(buf, sizeof buf, "sys: fp: %s", fpexcep); + postnote(up, 1, buf, NDebug); + } + } + + splhi(); + + /* delaysched set because we held a lock or because our quantum ended */ + if(up && up->delaysched && clockintr){ + sched(); + splhi(); + } + + if(user){ + notify(ur); + if(up->fpstate == FPinactive) { + restfpregs(&up->fpsave, up->fpsave.fpstatus&~FPEXCMASK); + up->fpstate = FPactive; + ur->status |= CU1; + } + kexit(ur); + } + + /* restore EXL in status */ + setstatus(getstatus() | EXL); +} + +/* periodically zero all the interrupt counts */ +static void +resetcounts(void) +{ + int i; + Handler *hp; + + ilock(&intrlock); + for (i = 0; i < nelem(handlers); i++) + for (hp = &handlers[i]; hp != nil; hp = hp->next) + hp->intrs = 0; + iunlock(&intrlock); +} + +/* + * set handlers + */ +void +intrenable(int irq, void (*h)(Ureg*, void*), void *arg, int subirq) +{ + Handler *hp; + static int resetclock; + + if (h == nil) + panic("intrenable: nil handler intr %d", irq); + if(irq < ILmin || irq >= nelem(handlers)) + panic("intrenable: bad handler intr %d %#p", irq, h); + + hp = &handlers[irq]; + ilock(&intrlock); + if (hp->handler != nil) { /* occupied? */ + /* add a new one at the end of the chain */ + for (; hp->next != nil; hp = hp->next) + ; + hp->next = smalloc(sizeof *hp); + hp = hp->next; + hp->next = nil; + } + hp->handler = h; + hp->arg = arg; + iunlock(&intrlock); + + if (irq == ILpci) { // enable pci sub-interrupt + *Pciintrsts = 0; + *Pciintrenset = 1 << subirq; + coherence(); + pciintrmask |= 1 << subirq; + } + + if (irq == IL8259) + { + i8259enable(subirq); + i8259intrmask |= 1 << subirq; + } + + intron(1 << (ILshift + irq)); + if (!resetclock) { + resetclock = 1; + addclock0link(resetcounts, 100); + } +} + +void +intrshutdown(void) +{ + introff(INTMASK); +} + +static void +jabberoff(Ureg *ur, int irq, ulong bit) +{ + introff(bit); /* interrupt off now ... */ + if (ur) + ur->status &= ~bit; /* ... and upon return */ + offintrs |= bit; + iprint("irq %d jabbering; shutting it down\n", irq); +} + +ulong +pollall(Ureg *ur, ulong cause) /* must be called splhi */ +{ + int i, intrs, sts, subirq; + ulong bit; + Handler *hp; + + /* exclude clock and sw intrs */ + intrs = cause & (INTR6|INTR5|INTR4|INTR3|INTR2) & getstatus(); + if(intrs == 0) + return cause; + + ilock(&intrlock); + for (i = ILmax; i >= ILmin; i--) { + bit = 1 << (ILshift + i); + if (!(intrs & bit)) + continue; + intrcauses[i]++; + for (hp = &handlers[i]; hp != nil; hp = hp->next) + if (hp->handler) { + if (i == ILpci) { + sts = *Pciintrsts & *Pciintren; + if((sts & pciintrmask) == 0) + continue; + // XXX need to clear sub-intr bits ? + //*Pciintrsts &= ~(1 << Pciintrether); + *Pciintrsts = 0; + } + + if (i == IL8259) { + subirq = i8259intack(); + if((1 << subirq) & i8259intrmask == 0) + continue; + i8259isr(subirq); + } + + (*hp->handler)(ur, hp->arg); + splhi(); + if (++hp->intrs > 25000) { + jabberoff(ur, i, bit); + intrs &= ~bit; + hp->intrs = 0; + } + } else if (ur) + iprint("no handler for interrupt %d\n", i); + cause &= ~bit; + } + iunlock(&intrlock); + return cause; +} + +int +intr(Ureg *ur) +{ + int clockintr; + ulong cause; + + m->intr++; + clockintr = 0; + /* + * ignore interrupts that we have disabled, even if their cause bits + * are set. + */ + cause = ur->cause & ur->status & INTMASK; + cause &= ~(INTR1|INTR0); /* ignore sw interrupts */ + if (cause == 0) + print("spurious interrupt\n"); + if(cause & INTR7){ + clock(ur); + intrcauses[ILclock]++; + cause &= ~(1 << (ILclock + ILshift)); + clockintr = 1; + } + cause = pollall(ur, cause); + if(cause){ + print("intr: cause %#lux not handled\n", cause); + exit(1); + } + + /* preemptive scheduling */ + if(up && !clockintr) + preempted(); + /* if it was a clockintr, sched will be called at end of trap() */ + return clockintr; +} + +void +kernfault(Ureg *ur, int code) +{ + print("panic: kfault %s badvaddr=%#lux", excname[code], ur->badvaddr); + kpteprint(ur); + print("u=%#p status=%#lux pc=%#lux sp=%#lux\n", + up, ur->status, ur->pc, ur->sp); + delay(500); + panic("kfault"); +} + +static void +getpcsp(ulong *pc, ulong *sp) +{ + *pc = getcallerpc(&pc); + *sp = (ulong)&pc-4; +} + +void +callwithureg(void (*fn)(Ureg*)) +{ + Ureg ureg; + + memset(&ureg, 0, sizeof ureg); + getpcsp((ulong*)&ureg.pc, (ulong*)&ureg.sp); + ureg.r31 = getcallerpc(&fn); + fn(&ureg); +} + +static void +_dumpstack(Ureg *ureg) +{ + ulong l, v, top, i; + extern ulong etext; + + if(up == 0) + return; + + print("ktrace /kernel/path %.8lux %.8lux %.8lux\n", + ureg->pc, ureg->sp, ureg->r31); + top = (ulong)up->kstack + KSTACK; + i = 0; + for(l=ureg->sp; l < top; l += BY2WD) { + v = *(ulong*)l; + if(KTZERO < v && v < (ulong)&etext) { + print("%.8lux=%.8lux ", l, v); + if((++i%4) == 0){ + print("\n"); + delay(200); + } + } + } + print("\n"); +} + +void +dumpstack(void) +{ + callwithureg(_dumpstack); +} + +static ulong +R(Ureg *ur, int i) +{ + uchar *s; + + s = (uchar*)ur; + return *(ulong*)(s + regname[i].off - Uoffset); +} + +void +dumpregs(Ureg *ur) +{ + int i; + + if(up) + print("registers for %s %lud\n", up->text, up->pid); + else + print("registers for kernel\n"); + + for(i = 0; i < nelem(regname); i += 2) + print("%s\t%#.8lux\t%s\t%#.8lux\n", + regname[i].name, R(ur, i), + regname[i+1].name, R(ur, i+1)); +} + +int +notify(Ureg *ur) +{ + int l, s; + ulong sp; + Note *n; + + if(up->procctl) + procctl(up); + if(up->nnote == 0) + return 0; + + s = spllo(); + qlock(&up->debug); + up->fpstate |= FPillegal; + up->notepending = 0; + n = &up->note[0]; + if(strncmp(n->msg, "sys:", 4) == 0) { + l = strlen(n->msg); + if(l > ERRMAX-15) /* " pc=0x12345678\0" */ + l = ERRMAX-15; + + seprint(n->msg+l, &n->msg[sizeof n->msg], " pc=%#lux", ur->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); + splx(s); + return 0; + } + + if(!up->notify) { + qunlock(&up->debug); + pexit(n->msg, n->flag!=NDebug); + } + sp = ur->usp - sizeof(Ureg) - BY2WD; /* libc adds 4 to usp */ + + if(!okaddr((ulong)up->notify, BY2WD, 0) || + !okaddr(sp-ERRMAX-4*BY2WD, sizeof(Ureg)+ERRMAX+4*BY2WD, 1)) { + pprint("suicide: bad address or sp in notify\n"); + qunlock(&up->debug); + pexit("Suicide", 0); + } + + memmove((Ureg*)sp, ur, sizeof(Ureg)); /* push user regs */ + *(Ureg**)(sp-BY2WD) = up->ureg; /* word under Ureg is old up->ureg */ + up->ureg = (void*)sp; + + sp -= BY2WD+ERRMAX; + memmove((char*)sp, up->note[0].msg, ERRMAX); /* push err string */ + + sp -= 3*BY2WD; + *(ulong*)(sp+2*BY2WD) = sp+3*BY2WD; /* arg 2 is string */ + ur->r1 = (long)up->ureg; /* arg 1 is ureg* */ + ((ulong*)sp)[1] = (ulong)up->ureg; /* arg 1 0(FP) is ureg* */ + ((ulong*)sp)[0] = 0; /* arg 0 is pc */ + ur->usp = sp; + /* + * arrange to resume at user's handler as if handler(ureg, errstr) + * were being called. + */ + ur->pc = (ulong)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; +} + +/* + * Check that status is OK to return from note. + */ +int +validstatus(ulong kstatus, ulong ustatus) +{ +// if((kstatus & (INTMASK|KX|SX|UX)) != (ustatus & (INTMASK|KX|SX|UX))) + if((kstatus & INTMASK) != (ustatus & INTMASK)) + return 0; + if((ustatus&(KSU|ERL|EXL|IE)) != (KUSER|EXL|IE)) + return 0; + if(ustatus & (0xFFFF0000&~CU1)) /* no CU3, CU2, CU0, RP, FR, RE, DS */ + return 0; + return 1; +} + +/* + * Return user to state before notify(); called from user's handler. + */ +void +noted(Ureg *kur, Ureg **urp, ulong arg0) +{ + Ureg *nur; + ulong oureg, sp; + + 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; + + up->fpstate &= ~FPillegal; + + nur = up->ureg; + + oureg = (ulong)nur; + if((oureg & (BY2WD-1)) + || !okaddr((ulong)oureg-BY2WD, BY2WD+sizeof(Ureg), 0)){ + pprint("bad up->ureg in noted or call to noted() when not notified\n"); + qunlock(&up->debug); + pexit("Suicide", 0); + } + + if(!validstatus(kur->status, nur->status)) { + qunlock(&up->debug); + pprint("bad noted ureg status %#lux\n", nur->status); + pexit("Suicide", 0); + } + + memmove(*urp, up->ureg, sizeof(Ureg)); + switch(arg0) { + case NCONT: + case NRSTR: /* only used by APE */ + if(!okaddr(nur->pc, BY2WD, 0) || !okaddr(nur->usp, BY2WD, 0)){ + pprint("suicide: trap in noted\n"); + qunlock(&up->debug); + pexit("Suicide", 0); + } + up->ureg = (Ureg*)(*(ulong*)(oureg-BY2WD)); + qunlock(&up->debug); + splhi(); + /* + * the old challenge and carrera ports called rfnote here, + * but newer ports do not, and notes seem to work only + * without this call. + */ + // rfnote(urp); /* return from note with SP=urp */ + break; + + case NSAVE: /* only used by APE */ + if(!okaddr(nur->pc, BY2WD, 0) || !okaddr(nur->usp, BY2WD, 0)){ + pprint("suicide: trap in noted\n"); + qunlock(&up->debug); + pexit("Suicide", 0); + } + qunlock(&up->debug); + sp = oureg-4*BY2WD-ERRMAX; + + splhi(); + (*urp)->sp = sp; + ((ulong*)sp)[1] = oureg; /* arg 1 0(FP) is ureg* */ + ((ulong*)sp)[0] = 0; /* arg 0 is pc */ + (*urp)->r1 = oureg; /* arg 1 is ureg* */ + + // rfnote(urp); /* return from note with SP=urp */ + break; + + default: + pprint("unknown noted arg %#lux\n", arg0); + up->lastnote.flag = NDebug; + /* fall through */ + + case NDFLT: + if(up->lastnote.flag == NDebug) + pprint("suicide: %s\n", up->lastnote.msg); + qunlock(&up->debug); + pexit(up->lastnote.msg, up->lastnote.flag!=NDebug); + } +} + +#include "../port/systab.h" + +static Ref goodsyscall; +static Ref totalsyscall; + +static void +sctracesetup(ulong scallnr, ulong sp, uintptr pc, vlong *startnsp) +{ + 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))) + validaddr(sp, sizeof(Sargs), 0); + + syscallfmt(scallnr, pc, (va_list)sp); + up->procctl = Proc_stopme; + procctl(up); + if(up->syscalltrace) + free(up->syscalltrace); + up->syscalltrace = nil; + *startnsp = todget(nil); + } +} + +static void +sctracefinish(ulong scallnr, ulong sp, int ret, vlong startns) +{ + int s; + + if(up->procctl == Proc_tracesyscall){ + up->procctl = Proc_stopme; + sysretfmt(scallnr, (va_list)sp, ret, + startns, todget(nil)); + s = splhi(); + procctl(up); + splx(s); + if(up->syscalltrace) + free(up->syscalltrace); + up->syscalltrace = nil; + } +} + +/* + * called directly from assembler, not via trap() + */ +long +syscall(Ureg *aur) +{ + int i; + volatile long ret; + ulong sp, scallnr; + vlong startns; + char *e; + Ureg *ur; + + cycles(&up->kentry); + + incref(&totalsyscall); + m->syscall++; + up->insyscall = 1; + ur = aur; + up->pc = ur->pc; + up->dbgreg = aur; + ur->cause = 16<<2; /* for debugging: system call is undef 16 */ + + scallnr = ur->r1; + up->scallnr = ur->r1; + sp = ur->sp; + sctracesetup(scallnr, sp, ur->pc, &startns); + + /* clear EXL in status */ + setstatus(getstatus() & ~EXL); + + if(up->fpstate == FPactive) { + if((ur->status & CU1) == 0) + panic("syscall: FPactive but no CU1"); + up->fpsave.fpstatus = fcr31(); + up->fpstate = FPinit; + ur->status &= ~CU1; + } + spllo(); + + up->nerrlab = 0; + ret = -1; + if(!waserror()) { + if(scallnr >= nsyscall || systab[scallnr] == 0){ + pprint("bad sys call number %ld pc %#lux\n", + scallnr, ur->pc); + postnote(up, 1, "sys: bad sys call", NDebug); + error(Ebadarg); + } + + if(sp & (BY2WD-1)){ + pprint("odd sp in sys call pc %#lux sp %#lux\n", + ur->pc, ur->sp); + postnote(up, 1, "sys: odd stack", NDebug); + error(Ebadarg); + } + + if(sp<(USTKTOP-BY2PG) || sp>(USTKTOP-sizeof(Sargs))) + validaddr(sp, sizeof(Sargs), 0); + + up->s = *(Sargs*)sp; /* spim's libc is different to mips ... */ + up->psstate = sysctab[scallnr]; + + 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 [%lud]: %d extra\n", scallnr, up->nerrlab); + for(i = 0; i < NERR; i++) + print("sp=%#lux pc=%#lux\n", + up->errlab[i].sp, up->errlab[i].pc); + panic("error stack"); + } + sctracefinish(scallnr, sp, ret, startns); + + ur->pc += 4; + ur->r1 = ret; + + up->psstate = 0; + up->insyscall = 0; + + if(scallnr == NOTED) { /* ugly hack */ + noted(ur, &aur, *(ulong*)sp); /* may return */ + ret = ur->r1; + } + incref(&goodsyscall); + splhi(); + if(scallnr!=RFORK && (up->procctl || up->nnote)){ + ur->r1 = ret; /* load up for noted() above */ + notify(ur); + } + /* if we delayed sched because we held a lock, sched now */ + if(up->delaysched) + sched(); + kexit(ur); + + /* restore EXL in status */ + setstatus(getstatus() | EXL); + + return ret; +} + +void +forkchild(Proc *p, Ureg *ur) +{ + Ureg *cur; + + p->sched.sp = (ulong)p->kstack+KSTACK-UREGSIZE; + p->sched.pc = (ulong)forkret; + + cur = (Ureg*)(p->sched.sp+2*BY2WD); + memmove(cur, ur, sizeof(Ureg)); + + cur->pc += 4; + + /* Things from bottom of syscall we never got to execute */ + p->psstate = 0; + p->insyscall = 0; +} + +static void +linkproc(void) +{ + spllo(); + up->kpfun(up->kparg); + pexit("kproc exiting", 0); +} + +void +kprocchild(Proc *p, void (*func)(void*), void *arg) +{ + p->sched.pc = (ulong)linkproc; + p->sched.sp = (ulong)p->kstack+KSTACK; + + p->kpfun = func; + p->kparg = arg; +} + +/* set up user registers before return from exec() */ +long +execregs(ulong entry, ulong ssize, ulong nargs) +{ + Ureg *ur; + ulong *sp; + + sp = (ulong*)(USTKTOP - ssize); + *--sp = nargs; + + ur = (Ureg*)up->dbgreg; + ur->usp = (ulong)sp; + ur->pc = entry - 4; /* syscall advances it */ + up->fpsave.fpstatus = initfp.fpstatus; + return USTKTOP-sizeof(Tos); /* address of kernel/user shared data */ +} + +ulong +userpc(void) +{ + Ureg *ur; + + ur = (Ureg*)up->dbgreg; + return ur->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 *xp, char *pureg, char *uva, int n) +{ + ulong status; + + status = xp->status; + memmove(pureg, uva, n); + xp->status = status; +} + +/* + * Give enough context in the ureg to produce a kernel stack for + * a sleeping process + */ +void +setkernur(Ureg *xp, Proc *p) +{ + xp->pc = p->sched.pc; + xp->sp = p->sched.sp; + xp->r24 = (ulong)p; /* up */ + xp->r31 = (ulong)sched; +} + +ulong +dbgpc(Proc *p) +{ + Ureg *ur; + + ur = p->dbgreg; + if(ur == 0) + return 0; + + return ur->pc; +} --- /dev/null +++ /sys/src/9/loongson/uarti8250.c @@ -0,0 +1,854 @@ +/* + * 8250-like UART + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +enum { + CONSOLE = 0, /* first uart */ + Pollstuckoutput = 1, +}; + +enum { /* registers */ + Rbr = 0, /* Receiver Buffer (RO) */ + Thr = 0, /* Transmitter Holding (WO) */ + Ier = 1, /* Interrupt Enable */ + Iir = 2, /* Interrupt Identification (RO) */ + Fcr = 2, /* FIFO Control (WO) */ + Lcr = 3, /* Line Control */ + Mcr = 4, /* Modem Control */ + Lsr = 5, /* Line Status */ + Msr = 6, /* Modem Status */ + + Scr = 7, /* Scratch Pad */ +// Mdr = 8, /* Mode Def'n (omap rw) */ + Usr = 31, /* Uart Status Register */ + Stickyend, + + Dll = 0, /* Divisor Latch LSB */ + Dlm = 1, /* Divisor Latch MSB */ +}; + +enum { /* Usr */ + Busy = 0x01, +}; + +enum { /* Ier */ + Erda = 0x01, /* Enable Received Data Available */ + Ethre = 0x02, /* Enable Thr Empty */ + Erls = 0x04, /* Enable Receiver Line Status */ + Ems = 0x08, /* Enable Modem Status */ +}; + +enum { /* Iir */ + Ims = 0x00, /* Ms interrupt */ + Ip = 0x01, /* Interrupt Pending (not) */ + Ithre = 0x02, /* Thr Empty */ + Irda = 0x04, /* Received Data Available */ + Irls = 0x06, /* Receiver Line Status */ + Ictoi = 0x0C, /* Character Time-out Indication */ + IirMASK = 0x3F, + Ifena = 0xC0, /* FIFOs enabled */ +}; + +enum { /* Fcr */ + FIFOena = 0x01, /* FIFO enable */ + FIFOrclr = 0x02, /* clear Rx FIFO */ + FIFOtclr = 0x04, /* clear Tx FIFO */ +// FIFOdma = 0x08, + FIFO1 = 0x00, /* Rx FIFO trigger level 1 byte */ + FIFO4 = 0x40, /* 4 bytes */ + FIFO8 = 0x80, /* 8 bytes */ + FIFO14 = 0xC0, /* 14 bytes */ +}; + +enum { /* Lcr */ + Wls5 = 0x00, /* Word Length Select 5 bits/byte */ + Wls6 = 0x01, /* 6 bits/byte */ + Wls7 = 0x02, /* 7 bits/byte */ + Wls8 = 0x03, /* 8 bits/byte */ + WlsMASK = 0x03, + Stb = 0x04, /* 2 stop bits */ + Pen = 0x08, /* Parity Enable */ + Eps = 0x10, /* Even Parity Select */ + Stp = 0x20, /* Stick Parity */ + Brk = 0x40, /* Break */ + Dlab = 0x80, /* Divisor Latch Access Bit */ +}; + +enum { /* Mcr */ + Dtr = 0x01, /* Data Terminal Ready */ + Rts = 0x02, /* Ready To Send */ + Out1 = 0x04, /* no longer in use */ + Ie = 0x08, /* IRQ Enable (cd_sts_ch on omap) */ + Dm = 0x10, /* Diagnostic Mode loopback */ +}; + +enum { /* Lsr */ + Dr = 0x01, /* Data Ready */ + Oe = 0x02, /* Overrun Error */ + Pe = 0x04, /* Parity Error */ + Fe = 0x08, /* Framing Error */ + Bi = 0x10, /* Break Interrupt */ + Thre = 0x20, /* Thr Empty */ + Temt = 0x40, /* Transmitter Empty */ + FIFOerr = 0x80, /* error in receiver FIFO */ +}; + +enum { /* Msr */ + Dcts = 0x01, /* Delta Cts */ + Ddsr = 0x02, /* Delta Dsr */ + Teri = 0x04, /* Trailing Edge of Ri */ + Ddcd = 0x08, /* Delta Dcd */ + Cts = 0x10, /* Clear To Send */ + Dsr = 0x20, /* Data Set Ready */ + Ri = 0x40, /* Ring Indicator */ + Dcd = 0x80, /* Carrier Detect */ +}; + +enum { /* Mdr */ + Modemask = 7, + Modeuart = 0, +}; + + +typedef struct Ctlr { + uchar* io; + int irq; + int tbdf; + int iena; + int poll; + + uchar sticky[Stickyend]; + + Lock; + int hasfifo; + int checkfifo; + int fena; +} Ctlr; + +extern PhysUart i8250physuart; + +static Ctlr i8250ctlr[] = { +{ .io = (uchar*)PHYSCONS, + .irq = ILduart0, + .tbdf = -1, + .poll = 0, }, +}; + +static Uart i8250uart[] = { +{ .regs = &i8250ctlr[0], + .name = "cons", + .freq = 1843200, /* Not used, we use the global i8250freq */ + .phys = &i8250physuart, + .console= 1, + .next = nil, }, +}; + +#define csr8r(c, r) ((c)->io[r]) +#define csr8w(c, r, v) ((c)->io[r] = (uchar)((c)->sticky[r] | (v))) +#define csr8o(c, r, v) ((c)->io[r] = (uchar)(v)) + +static long +i8250status(Uart* uart, void* buf, long n, long offset) +{ + char *p; + Ctlr *ctlr; + uchar ier, lcr, mcr, msr; + + ctlr = uart->regs; + p = smalloc(READSTR); + mcr = ctlr->sticky[Mcr]; + msr = csr8r(ctlr, Msr); + ier = ctlr->sticky[Ier]; + lcr = ctlr->sticky[Lcr]; + snprint(p, READSTR, + "b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n" + "dev(%d) type(%d) framing(%d) overruns(%d) " + "berr(%d) serr(%d)%s%s%s%s\n", + + uart->baud, + uart->hup_dcd, + (msr & Dsr) != 0, + uart->hup_dsr, + (lcr & WlsMASK) + 5, + (ier & Ems) != 0, + (lcr & Pen) ? ((lcr & Eps) ? 'e': 'o'): 'n', + (mcr & Rts) != 0, + (lcr & Stb) ? 2: 1, + ctlr->fena, + + uart->dev, + uart->type, + uart->ferr, + uart->oerr, + uart->berr, + uart->serr, + (msr & Cts) ? " cts": "", + (msr & Dsr) ? " dsr": "", + (msr & Dcd) ? " dcd": "", + (msr & Ri) ? " ring": "" + ); + n = readstr(offset, buf, n, p); + free(p); + + return n; +} + +static void +i8250fifo(Uart* uart, int level) +{ + Ctlr *ctlr; + + ctlr = uart->regs; + if(ctlr->hasfifo == 0) + return; + + /* + * Changing the FIFOena bit in Fcr flushes data + * from both receive and transmit FIFOs; there's + * no easy way to guarantee not losing data on + * the receive side, but it's possible to wait until + * the transmitter is really empty. + */ + ilock(ctlr); + while(!(csr8r(ctlr, Lsr) & Temt)) + ; + + /* + * Set the trigger level, default is the max. + * value. + * Some UARTs require FIFOena to be set before + * other bits can take effect, so set it twice. + */ + ctlr->fena = level; + switch(level){ + case 0: + break; + case 1: + level = FIFO1|FIFOena; + break; + case 4: + level = FIFO4|FIFOena; + break; + case 8: + level = FIFO8|FIFOena; + break; + default: + level = FIFO14|FIFOena; + break; + } + csr8w(ctlr, Fcr, level); + csr8w(ctlr, Fcr, level); + iunlock(ctlr); +} + +static void +i8250dtr(Uart* uart, int on) +{ + Ctlr *ctlr; + + /* + * Toggle DTR. + */ + ctlr = uart->regs; + if(on) + ctlr->sticky[Mcr] |= Dtr; + else + ctlr->sticky[Mcr] &= ~Dtr; + csr8w(ctlr, Mcr, 0); +} + +static void +i8250rts(Uart* uart, int on) +{ + Ctlr *ctlr; + + /* + * Toggle RTS. + */ + ctlr = uart->regs; + if(on) + ctlr->sticky[Mcr] |= Rts; + else + ctlr->sticky[Mcr] &= ~Rts; + csr8w(ctlr, Mcr, 0); +} + +static void +i8250modemctl(Uart* uart, int on) +{ + Ctlr *ctlr; + + ctlr = uart->regs; + ilock(&uart->tlock); + if(on){ + ctlr->sticky[Ier] |= Ems; + csr8w(ctlr, Ier, 0); + uart->modem = 1; + uart->cts = csr8r(ctlr, Msr) & Cts; + } + else{ + ctlr->sticky[Ier] &= ~Ems; + csr8w(ctlr, Ier, 0); + uart->modem = 0; + uart->cts = 1; + } + iunlock(&uart->tlock); + + /* modem needs fifo */ + (*uart->phys->fifo)(uart, on); +} + +static int +i8250parity(Uart* uart, int parity) +{ + int lcr; + Ctlr *ctlr; + + ctlr = uart->regs; + lcr = ctlr->sticky[Lcr] & ~(Eps|Pen); + + switch(parity){ + case 'e': + lcr |= Eps|Pen; + break; + case 'o': + lcr |= Pen; + break; + case 'n': + break; + default: + return -1; + } + ctlr->sticky[Lcr] = lcr; + csr8w(ctlr, Lcr, 0); + + uart->parity = parity; + + return 0; +} + +static int +i8250stop(Uart* uart, int stop) +{ + int lcr; + Ctlr *ctlr; + + ctlr = uart->regs; + lcr = ctlr->sticky[Lcr] & ~Stb; + + switch(stop){ + case 1: + break; + case 2: + lcr |= Stb; + break; + default: + return -1; + } + ctlr->sticky[Lcr] = lcr; + csr8w(ctlr, Lcr, 0); + + uart->stop = stop; + + return 0; +} + +static int +i8250bits(Uart* uart, int bits) +{ + int lcr; + Ctlr *ctlr; + + ctlr = uart->regs; + lcr = ctlr->sticky[Lcr] & ~WlsMASK; + + switch(bits){ + case 5: + lcr |= Wls5; + break; + case 6: + lcr |= Wls6; + break; + case 7: + lcr |= Wls7; + break; + case 8: + lcr |= Wls8; + break; + default: + return -1; + } + ctlr->sticky[Lcr] = lcr; + csr8w(ctlr, Lcr, 0); + + uart->bits = bits; + + return 0; +} + +static int +i8250baud(Uart* uart, int baud) +{ +#ifdef notdef /* don't change the speed */ + ulong bgc; + Ctlr *ctlr; + extern int i8250freq; /* In the config file */ + + /* + * Set the Baud rate by calculating and setting the Baud rate + * Generator Constant. This will work with fairly non-standard + * Baud rates. + */ + if(i8250freq == 0 || baud <= 0) + return -1; + bgc = (i8250freq+8*baud-1)/(16*baud); + + ctlr = uart->regs; + while(csr8r(ctlr, Usr) & Busy) + delay(1); + csr8w(ctlr, Lcr, Dlab); /* begin kludge */ + csr8o(ctlr, Dlm, bgc>>8); + csr8o(ctlr, Dll, bgc); + csr8w(ctlr, Lcr, 0); +#endif + uart->baud = baud; + return 0; +} + +static void +i8250break(Uart* uart, int ms) +{ + Ctlr *ctlr; + + if (up == nil) + panic("i8250break: nil up"); + /* + * Send a break. + */ + if(ms <= 0) + ms = 200; + + ctlr = uart->regs; + csr8w(ctlr, Lcr, Brk); + tsleep(&up->sleep, return0, 0, ms); + csr8w(ctlr, Lcr, 0); +} + +static void +emptyoutstage(Uart *uart, int n) +{ + _uartputs((char *)uart->op, n); + uart->op = uart->oe = uart->ostage; +} + +static void +i8250kick(Uart* uart) +{ + int i; + Ctlr *ctlr; + + if(/* uart->cts == 0 || */ uart->blocked) + return; + + if(!normalprint || 1) { /* early */ // XXX + if (uart->op < uart->oe) + emptyoutstage(uart, uart->oe - uart->op); + while ((i = uartstageoutput(uart)) > 0) + emptyoutstage(uart, i); + return; + } + + /* nothing more to send? then disable xmit intr */ + ctlr = uart->regs; + if (uart->op >= uart->oe && qlen(uart->oq) == 0 && + (1 || csr8r(ctlr, Lsr) & Temt)) { /* could try ignoring Temt */ + ctlr->sticky[Ier] &= ~Ethre; + csr8w(ctlr, Ier, 0); + return; + } + + /* + * 128 here is an arbitrary limit to make sure + * we don't stay in this loop too long. If the + * chip's output queue is longer than 128, too + * bad -- presotto + */ + for(i = 0; i < 128; i++){ + if(!(csr8r(ctlr, Lsr) & Thre)) + continue; // break; XXX + if(uart->op >= uart->oe && uartstageoutput(uart) == 0) + break; + csr8o(ctlr, Thr, *uart->op++); /* start tx */ + ctlr->sticky[Ier] |= Ethre; + csr8w(ctlr, Ier, 0); /* intr when done */ + } +} + +void +serialkick(void) +{ + uartkick(&i8250uart[CONSOLE]); +} + +static Lock i8250intrlock; + +static void +i8250interrupt(Ureg*, void* arg) +{ + Ctlr *ctlr; + Uart *uart; + int iir, lsr, old, r; + + uart = arg; + ctlr = uart->regs; + ilock(&i8250intrlock); + + /* force Ethre on. don't know why this is needed, but it is. */ + ctlr->sticky[Ier] |= Ethre; + csr8w(ctlr, Ier, 0); + /* this is probably optional. maybe it helps fast input. */ + ctlr->sticky[Mcr] |= Ie; + csr8w(ctlr, Mcr, 0); + + for(iir = csr8r(ctlr, Iir); !(iir & Ip); iir = csr8r(ctlr, Iir)){ + switch(iir & IirMASK){ + case Ims: /* Ms interrupt */ + r = csr8r(ctlr, Msr); + if(r & Dcts){ + ilock(&uart->tlock); + old = uart->cts; + uart->cts = r & Cts; + if(old == 0 && uart->cts) + uart->ctsbackoff = 2; + iunlock(&uart->tlock); + } + if(r & Ddsr){ + old = r & Dsr; + if(uart->hup_dsr && uart->dsr && !old) + uart->dohup = 1; + uart->dsr = old; + } + if(r & Ddcd){ + old = r & Dcd; + if(uart->hup_dcd && uart->dcd && !old) + uart->dohup = 1; + uart->dcd = old; + } + break; + case Ithre: /* Thr Empty */ + uartkick(uart); + break; + case Irda: /* Received Data Available */ + case Irls: /* Receiver Line Status */ + case Ictoi: /* Character Time-out Indication */ + /* + * Consume any received data. + * If the received byte came in with a break, + * parity or framing error, throw it away; + * overrun is an indication that something has + * already been tossed. + */ + while((lsr = csr8r(ctlr, Lsr)) & Dr){ + if(lsr & (FIFOerr|Oe)) + uart->oerr++; + if(lsr & Pe) + uart->perr++; + if(lsr & Fe) + uart->ferr++; + r = csr8r(ctlr, Rbr); + if(!(lsr & (Bi|Fe|Pe))) + uartrecv(uart, r); + } + break; + + default: + iprint("weird uart interrupt type %#2.2uX\n", iir); + break; + } + } + iunlock(&i8250intrlock); +} + +static void +i8250disable(Uart* uart) +{ + Ctlr *ctlr; + + /* + * Turn off DTR and RTS, disable interrupts and fifos. + */ + (*uart->phys->dtr)(uart, 0); + (*uart->phys->rts)(uart, 0); + (*uart->phys->fifo)(uart, 0); + + ctlr = uart->regs; + ctlr->sticky[Ier] = 0; + csr8w(ctlr, Ier, 0); + + if(ctlr->iena != 0){ + /* bad idea if the IRQ is shared */ +// introff(1 << (ILshift + ctlr->irq)); + ctlr->iena = 0; + } +} + +static void +i8250clock(void) +{ + i8250interrupt(nil, &i8250uart[CONSOLE]); +} + +static void +i8250enable(Uart* uart, int ie) +{ + Ctlr *ctlr; + + ctlr = uart->regs; + ctlr->sticky[Lcr] = Wls8; /* no parity */ + csr8w(ctlr, Lcr, 0); + + /* + * Check if there is a FIFO. + * Changing the FIFOena bit in Fcr flushes data + * from both receive and transmit FIFOs; there's + * no easy way to guarantee not losing data on + * the receive side, but it's possible to wait until + * the transmitter is really empty. + * Also, reading the Iir outwith i8250interrupt() + * can be dangerous, but this should only happen + * once, before interrupts are enabled. + */ + ilock(ctlr); + if(!ctlr->checkfifo){ + /* + * Wait until the transmitter is really empty. + */ + while(!(csr8r(ctlr, Lsr) & Temt)) + ; + csr8w(ctlr, Fcr, FIFOena); + if(csr8r(ctlr, Iir) & Ifena) + ctlr->hasfifo = 1; + csr8w(ctlr, Fcr, 0); + ctlr->checkfifo = 1; + } + iunlock(ctlr); + + /* + * Enable interrupts and turn on DTR and RTS. + * Be careful if this is called to set up a polled serial line + * early on not to try to enable interrupts as interrupt- + * -enabling mechanisms might not be set up yet. + */ + if(ie){ + if(ctlr->iena == 0 && !ctlr->poll){ + intrenable(ctlr->irq, i8250interrupt, uart, 0); + ctlr->iena = 1; + } + ctlr->sticky[Ier] = Erda; + ctlr->sticky[Mcr] |= Ie; + } + else{ + ctlr->sticky[Ier] = 0; + ctlr->sticky[Mcr] = 0; + } + csr8w(ctlr, Ier, 0); + csr8w(ctlr, Mcr, 0); + + (*uart->phys->dtr)(uart, 1); + (*uart->phys->rts)(uart, 1); + + /* + * During startup, the i8259 interrupt controller is reset. + * This may result in a lost interrupt from the i8250 uart. + * The i8250 thinks the interrupt is still outstanding and does not + * generate any further interrupts. The workaround is to call the + * interrupt handler to clear any pending interrupt events. + * Note: this must be done after setting Ier. + */ + if(ie) { + i8250interrupt(nil, uart); + /* + * force output to resume if stuck. shouldn't be needed. + */ + if (Pollstuckoutput) + addclock0link(i8250clock, 10); + } +} + +static Uart* +i8250pnp(void) +{ + return i8250uart; +} + +static int +i8250getc(Uart* uart) +{ + Ctlr *ctlr; + + ctlr = uart->regs; + while(!(csr8r(ctlr, Lsr) & Dr)) + delay(1); + return csr8r(ctlr, Rbr); +} + +static void +i8250putc(Uart* uart, int c) +{ + int i, s; + Ctlr *ctlr; + + if (!normalprint) { /* too early; use brute force */ + s = splhi(); + while (!(((uchar*)PHYSCONS)[Lsr] & Thre)) + ; + ((uchar*)PHYSCONS)[Thr] = (uchar)c; + splx(s); + return; + } + + ctlr = uart->regs; + s = splhi(); + for(i = 0; !(csr8r(ctlr, Lsr) & Thre) && i < 200; i++) + delay(5); + csr8o(ctlr, Thr, c); + for(i = 0; !(csr8r(ctlr, Lsr) & Thre) && i < 200; i++) + delay(5); + splx(s); +} + +void +serialputc(int c) +{ + i8250putc(&i8250uart[CONSOLE], c); +} + +void +serialputs(char* s, int n) +{ + _uartputs(s, n); +} + +#ifdef PLAN9K +static void +i8250poll(Uart* uart) +{ + Ctlr *ctlr; + + /* + * If PhysUart has a non-nil .poll member, this + * routine will be called from the uartclock timer. + * If the Ctlr .poll member is non-zero, when the + * Uart is enabled interrupts will not be enabled + * and the result is polled input and output. + * Not very useful here, but ports to new hardware + * or simulators can use this to get serial I/O + * without setting up the interrupt mechanism. + */ + ctlr = uart->regs; + if(ctlr->iena || !ctlr->poll) + return; + i8250interrupt(nil, uart); +} +#endif + +PhysUart i8250physuart = { + .name = "i8250", + .pnp = i8250pnp, + .enable = i8250enable, + .disable = i8250disable, + .kick = i8250kick, + .dobreak = i8250break, + .baud = i8250baud, + .bits = i8250bits, + .stop = i8250stop, + .parity = i8250parity, + .modemctl = i8250modemctl, + .rts = i8250rts, + .dtr = i8250dtr, + .status = i8250status, + .fifo = i8250fifo, + .getc = i8250getc, + .putc = i8250putc, +#ifdef PLAN9K + .poll = i8250poll, +#endif +}; + +static void +i8250dumpregs(Ctlr* ctlr) +{ + int dlm, dll; + int _uartprint(char*, ...); + + csr8w(ctlr, Lcr, Dlab); + dlm = csr8r(ctlr, Dlm); + dll = csr8r(ctlr, Dll); + csr8w(ctlr, Lcr, 0); + + _uartprint("dlm %#ux dll %#ux\n", dlm, dll); +} + +Uart* uartenable(Uart *p); + +/* must call this from a process's context */ +int +i8250console(void) +{ + Uart *uart; + + if (up == nil) + return -1; /* too early */ + + uart = &i8250uart[CONSOLE]; + if(uartenable(uart) != nil && uart->console){ + kbdq = uart->iq; + assert(kbdq); + serialoq = uart->oq; + assert(serialoq); + uart->putc = kbdcr2nl; + uart->opens++; + consuart = uart; + /* up wasn't set when chandevreset ran, so enable it now */ + i8250disable(uart); + i8250enable(uart, 1); + } + uartctl(uart, "b115200 l8 pn m0 s1 i128 w100"); + return 0; +} + +void +_uartputs(char* s, int n) +{ + char *e; + + for(e = s+n; s < e; s++){ + if(*s == '\n') + i8250putc(&i8250uart[CONSOLE], '\r'); + i8250putc(&i8250uart[CONSOLE], *s); + } +} + +int +_uartprint(char* fmt, ...) +{ + int n; + va_list arg; + char buf[PRINTSIZE]; + + va_start(arg, fmt); + n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf; + va_end(arg); + _uartputs(buf, n); + + return n; +} + +void (*lprint)(char *, int) = _uartputs; --- /dev/null +++ /sys/src/9/loongson/usbohci.c @@ -0,0 +1,2570 @@ +/* + * USB Open Host Controller Interface (Ohci) driver + * + * BUGS: + * - Missing isochronous input streams. + * - Too many delays and ilocks. + * - bandwidth admission control must be done per-frame. + * - Buffering could be handled like in uhci, to avoid + * needed block allocation and avoid allocs for small Tds. + * - must warn of power overruns. + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +#include "../port/usb.h" + +typedef struct Ctlio Ctlio; +typedef struct Ctlr Ctlr; +typedef struct Ed Ed; +typedef struct Edpool Edpool; +typedef struct Epx Epx; +typedef struct Hcca Hcca; +typedef struct Isoio Isoio; +typedef struct Ohci Ohci; +typedef struct Qio Qio; +typedef struct Qtree Qtree; +typedef struct Td Td; +typedef struct Tdpool Tdpool; + +enum +{ + Incr = 64, /* for Td and Ed pools */ + + Align = 0x20, /* OHCI only requires 0x10 */ + /* use always a power of 2 */ + + Abortdelay = 1, /* delay after cancelling Tds (ms) */ + Tdatomic = 8, /* max nb. of Tds per bulk I/O op. */ + Enabledelay = 100, /* waiting for a port to enable */ + + + /* Queue states (software) */ + Qidle = 0, + Qinstall, + Qrun, + Qdone, + Qclose, + Qfree, + + /* Ed control bits */ + Edmpsmask = 0x7ff, /* max packet size */ + Edmpsshift = 16, + Edlow = 1 << 13, /* low speed */ + Edskip = 1 << 14, /* skip this ed */ + Ediso = 1 << 15, /* iso Tds used */ + Edtddir = 0, /* get dir from td */ + Edin = 2 << 11, /* direction in */ + Edout = 1 << 11, /* direction out */ + Eddirmask = 3 << 11, /* direction bits */ + Edhalt = 1, /* halted (in head ptr) */ + Edtoggle = 2, /* toggle (in head ptr) 1 == data1 */ + + /* Td control bits */ + Tdround = 1<<18, /* (rounding) short packets ok */ + Tdtoksetup = 0<<19, /* setup packet */ + Tdtokin = 2<<19, /* in packet */ + Tdtokout = 1<<19, /* out packet */ + Tdtokmask = 3<<19, /* in/out/setup bits */ + Tdnoioc = 7<<21, /* intr. cnt. value for no interrupt */ + Tdusetog = 1<<25, /* use toggle from Td (1) or Ed (0) */ + Tddata1 = 1<<24, /* data toggle (1 == data1) */ + Tddata0 = 0<<24, + Tdfcmask = 7, /* frame count (iso) */ + Tdfcshift = 24, + Tdsfmask = 0xFFFF, /* starting frame (iso) */ + Tderrmask = 3, /* error counter */ + Tderrshift = 26, + Tdccmask = 0xf, /* condition code (status) */ + Tdccshift = 28, + Tdiccmask = 0xf, /* condition code (iso, offsets) */ + Tdiccshift = 12, + + Ntdframes = 0x10000, /* # of different iso frame numbers */ + + /* Td errors (condition code) */ + Tdok = 0, + Tdcrc = 1, + Tdbitstuff = 2, + Tdbadtog = 3, + Tdstalled = 4, + Tdtmout = 5, + Tdpidchk = 6, + Tdbadpid = 7, + Tddataovr = 8, + Tddataund = 9, + Tdbufovr = 0xC, + Tdbufund = 0xD, + Tdnotacc = 0xE, + + /* control register */ + Cple = 0x04, /* periodic list enable */ + Cie = 0x08, /* iso. list enable */ + Ccle = 0x10, /* ctl list enable */ + Cble = 0x20, /* bulk list enable */ + Cfsmask = 3 << 6, /* functional state... */ + Cfsreset = 0 << 6, + Cfsresume = 1 << 6, + Cfsoper = 2 << 6, + Cfssuspend = 3 << 6, + + /* command status */ + Sblf = 1 << 2, /* bulk list (load) flag */ + Sclf = 1 << 1, /* control list (load) flag */ + Shcr = 1 << 0, /* host controller reset */ + + /* intr enable */ + Mie = 1 << 31, + Oc = 1 << 30, + Rhsc = 1 << 6, + Fno = 1 << 5, + Ue = 1 << 4, + Rd = 1 << 3, + Sf = 1 << 2, + Wdh = 1 << 1, + So = 1 << 0, + + Fmaxpktmask = 0x7fff, + Fmaxpktshift = 16, + HcRhDescA_POTPGT_MASK = 0xff << 24, + HcRhDescA_POTPGT_SHIFT = 24, + + /* Rh status */ + Lps = 1 << 0, + Cgp = 1 << 0, + Oci = 1 << 1, + Psm = 1 << 8, + Nps = 1 << 9, + Drwe = 1 << 15, + Srwe = 1 << 15, + Lpsc = 1 << 16, + Ccic = 1 << 17, + Crwe = 1 << 31, + + /* port status */ + Ccs = 0x00001, /* current connect status */ + Pes = 0x00002, /* port enable status */ + Pss = 0x00004, /* port suspend status */ + Poci = 0x00008, /* over current indicator */ + Prs = 0x00010, /* port reset status */ + Pps = 0x00100, /* port power status */ + Lsda = 0x00200, /* low speed device attached */ + Csc = 0x10000, /* connect status change */ + Pesc = 0x20000, /* enable status change */ + Pssc = 0x40000, /* suspend status change */ + Ocic = 0x80000, /* over current ind. change */ + Prsc = 0x100000, /* reset status change */ + + /* port status write bits */ + Cpe = 0x001, /* clear port enable */ + Spe = 0x002, /* set port enable */ + Spr = 0x010, /* set port reset */ + Spp = 0x100, /* set port power */ + Cpp = 0x200, /* clear port power */ + +}; + +/* + * Endpoint descriptor. (first 4 words used by hardware) + */ +struct Ed { + ulong ctrl; + ulong tail; /* transfer descriptor */ + ulong head; + ulong nexted; + + Ed* next; /* sw; in free list or next in list */ + Td* tds; /* in use by current xfer; all for iso */ + Ep* ep; /* debug/align */ + Ed* inext; /* debug/align (dump interrupt eds). */ +}; + +/* + * Endpoint I/O state (software), per direction. + */ +struct Qio +{ + QLock; /* for the entire I/O process */ + Rendez; /* wait for completion */ + Ed* ed; /* to place Tds on it */ + int sched; /* queue number (intr/iso) */ + int toggle; /* Tddata0/Tddata1 */ + ulong usbid; /* device/endpoint address */ + int tok; /* Tdsetup, Tdtokin, Tdtokout */ + long iotime; /* last I/O time; to hold interrupt polls */ + int debug; /* for the endpoint */ + char* err; /* error status */ + int state; /* Qidle -> Qinstall -> Qrun -> Qdone | Qclose */ + long bw; /* load (intr/iso) */ +}; + +struct Ctlio +{ + Qio; /* single Ed for all transfers */ + uchar* data; /* read from last ctl req. */ + int ndata; /* number of bytes read */ +}; + +struct Isoio +{ + Qio; + int nframes; /* number of frames for a full second */ + Td* atds; /* Tds avail for further I/O */ + int navail; /* number of avail Tds */ + ulong frno; /* next frame number avail for I/O */ + ulong left; /* remainder after rounding Hz to samples/ms */ + int nerrs; /* consecutive errors on iso I/O */ +}; + +/* + * Transfer descriptor. Size must be multiple of 32 + * First block is used by hardware (aligned to 32). + */ +struct Td +{ + ulong ctrl; + ulong cbp; /* current buffer pointer */ + ulong nexttd; + ulong be; + ushort offsets[8]; /* used by Iso Tds only */ + + Td* next; /* in free or Ed tds list */ + Td* anext; /* in avail td list (iso) */ + Ep* ep; /* using this Td for I/O */ + Qio* io; /* using this Td for I/O */ + Block* bp; /* data for this Td */ + ulong nbytes; /* bytes in this Td */ + ulong cbp0; /* initial value for cbp */ + ulong last; /* true for last Td in Qio */ +}; + +/* + * Host controller communication area (hardware) + */ +struct Hcca +{ + ulong intrtable[32]; + ushort framenumber; + ushort pad1; + ulong donehead; + uchar reserved[116]; +}; + +/* + * I/O registers + */ +struct Ohci +{ + /* control and status group */ + ulong revision; /*00*/ + ulong control; /*04*/ + ulong cmdsts; /*08*/ + ulong intrsts; /*0c*/ + ulong intrenable; /*10*/ + ulong intrdisable; /*14*/ + + /* memory pointer group */ + ulong hcca; /*18*/ + ulong periodcurred; /*1c*/ + ulong ctlheaded; /*20*/ + ulong ctlcurred; /*24*/ + ulong bulkheaded; /*28*/ + ulong bulkcurred; /*2c*/ + ulong donehead; /*30*/ + + /* frame counter group */ + ulong fminterval; /*34*/ + ulong fmremaining; /*38*/ + ulong fmnumber; /*3c*/ + ulong periodicstart; /*40*/ + ulong lsthreshold; /*44*/ + + /* root hub group */ + ulong rhdesca; /*48*/ + ulong rhdescb; /*4c*/ + ulong rhsts; /*50*/ + ulong rhportsts[15]; /*54*/ + ulong pad25[20]; /*90*/ + + /* unknown */ + ulong hostueaddr; /*e0*/ + ulong hostuests; /*e4*/ + ulong hosttimeoutctrl; /*e8*/ + ulong pad59; /*ec*/ + ulong pad60; /*f0*/ + ulong hostrevision; /*f4*/ + ulong pad62[2]; + /*100*/ +}; + +/* + * Endpoint tree (software) + */ +struct Qtree +{ + int nel; + int depth; + ulong* bw; + Ed** root; +}; + +struct Tdpool +{ + Lock; + Td* free; + int nalloc; + int ninuse; + int nfree; +}; + +struct Edpool +{ + Lock; + Ed* free; + int nalloc; + int ninuse; + int nfree; +}; + +struct Ctlr +{ + Lock; /* for ilock; lists and basic ctlr I/O */ + QLock resetl; /* lock controller during USB reset */ + int active; + Ctlr* next; + int nports; + + Ohci* ohci; /* base I/O address */ + Hcca* hcca; /* intr/done Td lists (used by hardware) */ + int overrun; /* sched. overrun */ + Ed* intrhd; /* list of intr. eds in tree */ + Qtree* tree; /* tree for t Ep i/o */ + int ntree; /* number of dummy Eds in tree */ + Pcidev* pcidev; +}; + +#define dqprint if(debug || io && io->debug)print +#define ddqprint if(debug>1 || (io && io->debug>1))print +#define diprint if(debug || iso && iso->debug)print +#define ddiprint if(debug>1 || (iso && iso->debug>1))print +#define TRUNC(x, sz) ((x) & ((sz)-1)) + +static int ohciinterrupts[Nttypes]; +static char* iosname[] = { "idle", "install", "run", "done", "close", "FREE" }; + +static int debug; +static Edpool edpool; +static Tdpool tdpool; +static Ctlr* ctlrs[Nhcis]; + +static QLock usbhstate; /* protects name space state */ + +static int schedendpt(Ctlr *ub, Ep *ep); +static void unschedendpt(Ctlr *ub, Ep *ep); +static long qtd(Ctlr*, Ep*, int, Block*, uchar*, uchar*, int, ulong); + +static char* errmsgs[] = +{ +[Tdcrc] "crc error", +[Tdbitstuff] "bit stuffing error", +[Tdbadtog] "bad toggle", +[Tdstalled] Estalled, +[Tdtmout] "timeout error", +[Tdpidchk] "pid check error", +[Tdbadpid] "bad pid", +[Tddataovr] "data overrun", +[Tddataund] "data underrun", +[Tdbufovr] "buffer overrun", +[Tdbufund] "buffer underrun", +[Tdnotacc] "not accessed" +}; + +static void* +pa2ptr(ulong pa) +{ + if(pa == 0) + return nil; + else + return KSEG1ADDR(pa); +} + +static ulong +ptr2pa(void *p) +{ + if(p == nil) + return 0; + else + return PCIWADDR(p); // XXX +} + +static void +waitSOF(Ctlr *ub) +{ + int frame = ub->hcca->framenumber & 0x3f; + + do { + delay(2); + } while(frame == (ub->hcca->framenumber & 0x3f)); +} + +static char* +errmsg(int err) +{ + + if(err < nelem(errmsgs)) + return errmsgs[err]; + return nil; +} + +static Ed* +ctlhd(Ctlr *ctlr) +{ + return pa2ptr(ctlr->ohci->ctlheaded); +} + +static Ed* +bulkhd(Ctlr *ctlr) +{ + return pa2ptr(ctlr->ohci->bulkheaded); +} + +static void +edlinked(Ed *ed, Ed *next) +{ + if(ed == nil) + print("edlinked: nil ed: pc %#p\n", getcallerpc(&ed)); + ed->nexted = ptr2pa(next); + ed->next = next; +} + +static void +setctlhd(Ctlr *ctlr, Ed *ed) +{ + ctlr->ohci->ctlheaded = ptr2pa(ed); + if(ed != nil) + ctlr->ohci->cmdsts |= Sclf; /* reload it on next pass */ +} + +static void +setbulkhd(Ctlr *ctlr, Ed *ed) +{ + ctlr->ohci->bulkheaded = ptr2pa(ed); + if(ed != nil) + ctlr->ohci->cmdsts |= Sblf; /* reload it on next pass */ +} + +static void +unlinkctl(Ctlr *ctlr, Ed *ed) +{ + Ed *this, *prev, *next; + + ctlr->ohci->control &= ~Ccle; + waitSOF(ctlr); + this = ctlhd(ctlr); + ctlr->ohci->ctlcurred = 0; + prev = nil; + while(this != nil && this != ed){ + prev = this; + this = this->next; + } + if(this == nil){ + print("unlinkctl: not found\n"); + return; + } + next = this->next; + if(prev == nil) + setctlhd(ctlr, next); + else + edlinked(prev, next); + ctlr->ohci->control |= Ccle; + edlinked(ed, nil); /* wipe out next field */ +} + +static void +unlinkbulk(Ctlr *ctlr, Ed *ed) +{ + Ed *this, *prev, *next; + + ctlr->ohci->control &= ~Cble; + waitSOF(ctlr); + this = bulkhd(ctlr); + ctlr->ohci->bulkcurred = 0; + prev = nil; + while(this != nil && this != ed){ + prev = this; + this = this->next; + } + if(this == nil){ + print("unlinkbulk: not found\n"); + return; + } + next = this->next; + if(prev == nil) + setbulkhd(ctlr, next); + else + edlinked(prev, next); + ctlr->ohci->control |= Cble; + edlinked(ed, nil); /* wipe out next field */ +} + +static void +edsetaddr(Ed *ed, ulong addr) +{ + ulong ctrl; + + ctrl = ed->ctrl & ~((Epmax<<7)|Devmax); + ctrl |= (addr & ((Epmax<<7)|Devmax)); + ed->ctrl = ctrl; +} + +static void +edsettog(Ed *ed, int c) +{ + if(c != 0) + ed->head |= Edtoggle; + else + ed->head &= ~Edtoggle; +} + +static int +edtoggle(Ed *ed) +{ + return ed->head & Edtoggle; +} + +static int +edhalted(Ed *ed) +{ + return ed->head & Edhalt; +} + +static int +edmaxpkt(Ed *ed) +{ + return (ed->ctrl >> Edmpsshift) & Edmpsmask; +} + +static void +edsetmaxpkt(Ed *ed, int m) +{ + ulong c; + + c = ed->ctrl & ~(Edmpsmask << Edmpsshift); + ed->ctrl = c | ((m&Edmpsmask) << Edmpsshift); +} + +static int +tderrs(Td *td) +{ + return (td->ctrl >> Tdccshift) & Tdccmask; +} + +static int +tdtok(Td *td) +{ + return (td->ctrl & Tdtokmask); +} + +static Td* +tdalloc(void) +{ + Td *td; + Td *pool; + int i; + + lock(&tdpool); + if(tdpool.free == nil){ + ddprint("ohci: tdalloc %d Tds\n", Incr); + pool = xspanalloc(Incr*sizeof(Td), Align, 0); + if(pool == nil) + panic("tdalloc"); + pool = KSEG1ADDR(pool); // XXX + for(i=Incr; --i>=0;){ + pool[i].next = tdpool.free; + tdpool.free = &pool[i]; + } + tdpool.nalloc += Incr; + tdpool.nfree += Incr; + } + tdpool.ninuse++; + tdpool.nfree--; + td = tdpool.free; + tdpool.free = td->next; + memset(td, 0, sizeof(Td)); + unlock(&tdpool); + + assert(((uintptr)td & 0xF) == 0); + return td; +} + +static void +tdfree(Td *td) +{ + if(td == 0) + return; + freeb(td->bp); + td->bp = nil; + lock(&tdpool); + if(td->nexttd == 0x77777777) + panic("ohci: tdfree: double free"); + memset(td, 7, sizeof(Td)); /* poison */ + td->next = tdpool.free; + tdpool.free = td; + tdpool.ninuse--; + tdpool.nfree++; + unlock(&tdpool); +} + +static Ed* +edalloc(void) +{ + Ed *ed, *pool; + int i; + + lock(&edpool); + if(edpool.free == nil){ + ddprint("ohci: edalloc %d Eds\n", Incr); + pool = xspanalloc(Incr*sizeof(Ed), Align, 0); + if(pool == nil) + panic("edalloc"); + pool = KSEG1ADDR(pool); // XXX + for(i=Incr; --i>=0;){ + pool[i].next = edpool.free; + edpool.free = &pool[i]; + } + edpool.nalloc += Incr; + edpool.nfree += Incr; + } + edpool.ninuse++; + edpool.nfree--; + ed = edpool.free; + edpool.free = ed->next; + memset(ed, 0, sizeof(Ed)); + unlock(&edpool); + + return ed; +} + +static void +edfree(Ed *ed) +{ + Td *td, *next; + int i; + + if(ed == 0) + return; + i = 0; + for(td = ed->tds; td != nil; td = next){ + next = td->next; + tdfree(td); + if(i++ > 2000){ + print("ohci: bug: ed with more than 2000 tds\n"); + break; + } + } + lock(&edpool); + if(ed->nexted == 0x99999999) + panic("ohci: edfree: double free"); + memset(ed, 9, sizeof(Ed)); /* poison */ + ed->next = edpool.free; + edpool.free = ed; + edpool.ninuse--; + edpool.nfree++; + unlock(&edpool); + ddprint("edfree: ed %#p\n", ed); +} + +/* + * return smallest power of 2 >= n + */ +static int +flog2(int n) +{ + int i; + + for(i = 0; (1 << i) < n; i++) + ; + return i; +} + +/* + * return smallest power of 2 <= n + */ +static int +flog2lower(int n) +{ + int i; + + for(i = 0; (1 << (i + 1)) <= n; i++) + ; + return i; +} + +static int +pickschedq(Qtree *qt, int pollival, ulong bw, ulong limit) +{ + int i, j, d, upperb, q; + ulong best, worst, total; + + d = flog2lower(pollival); + if(d > qt->depth) + d = qt->depth; + q = -1; + worst = 0; + best = ~0; + upperb = (1 << (d+1)) - 1; + for(i = (1 << d) - 1; i < upperb; i++){ + total = qt->bw[0]; + for(j = i; j > 0; j = (j - 1) / 2) + total += qt->bw[j]; + if(total < best){ + best = total; + q = i; + } + if(total > worst) + worst = total; + } + if(worst + bw >= limit) + return -1; + return q; +} + +static int +schedq(Ctlr *ctlr, Qio *io, int pollival) +{ + int q; + Ed *ted; + + q = pickschedq(ctlr->tree, pollival, io->bw, ~0); + ddqprint("ohci: sched %#p q %d, ival %d, bw %ld\n", io, q, pollival, io->bw); + if(q < 0){ + print("ohci: no room for ed\n"); + return -1; + } + ctlr->tree->bw[q] += io->bw; + ted = ctlr->tree->root[q]; + io->sched = q; + edlinked(io->ed, ted->next); + edlinked(ted, io->ed); + io->ed->inext = ctlr->intrhd; + ctlr->intrhd = io->ed; + return 0; +} + +static void +unschedq(Ctlr *ctlr, Qio *qio) +{ + int q; + Ed *prev, *this, *next; + Ed **l; + + q = qio->sched; + if(q < 0) + return; + ctlr->tree->bw[q] -= qio->bw; + + prev = ctlr->tree->root[q]; + this = prev->next; + while(this != nil && this != qio->ed){ + prev = this; + this = this->next; + } + if(this == nil) + print("ohci: unschedq %d: not found\n", q); + else{ + next = this->next; + edlinked(prev, next); + } + waitSOF(ctlr); + for(l = &ctlr->intrhd; *l != nil; l = &(*l)->inext) + if(*l == qio->ed){ + *l = (*l)->inext; + return; + } + print("ohci: unschedq: ed %#p not found\n", qio->ed); +} + +static char* +seprinttdtok(char *s, char *e, int tok) +{ + switch(tok){ + case Tdtoksetup: + s = seprint(s, e, " setup"); + break; + case Tdtokin: + s = seprint(s, e, " in"); + break; + case Tdtokout: + s = seprint(s, e, " out"); + break; + } + return s; +} + + +static char* +seprinttd(char *s, char *e, Td *td, int iso) +{ + int i; + Block *bp; + + if(td == nil) + return seprint(s, e, "\n"); + s = seprint(s, e, "%#p ep %#p ctrl %#p", td, td->ep, td->ctrl); + s = seprint(s, e, " cc=%#ulx", (td->ctrl >> Tdccshift) & Tdccmask); + if(iso == 0){ + if((td->ctrl & Tdround) != 0) + s = seprint(s, e, " rnd"); + s = seprinttdtok(s, e, td->ctrl & Tdtokmask); + if((td->ctrl & Tdusetog) != 0) + s = seprint(s, e, " d%d", (td->ctrl & Tddata1) ? 1 : 0); + else + s = seprint(s, e, " d-"); + s = seprint(s, e, " ec=%uld", (td->ctrl >> Tderrshift) & Tderrmask); + }else{ + s = seprint(s, e, " fc=%uld", (td->ctrl >> Tdfcshift) & Tdfcmask); + s = seprint(s, e, " sf=%uld", td->ctrl & Tdsfmask); + } + s = seprint(s, e, " cbp0 %#p cbp %#p next %#p be %#p %s", + td->cbp0, td->cbp, td->nexttd, td->be, td->last ? "last" : ""); + s = seprint(s, e, "\n\t\t%ld bytes", td->nbytes); + if((bp = td->bp) != nil){ + s = seprint(s, e, " rp %#p wp %#p ", bp->rp, bp->wp); + if(BLEN(bp) > 0) + s = seprintdata(s, e, bp->rp, bp->wp - bp->rp); + } + if(iso == 0) + return seprint(s, e, "\n"); + s = seprint(s, e, "\n\t\t"); + /* we use only offsets[0] */ + i = 0; + s = seprint(s, e, "[%d] %#ux cc=%#ux sz=%ud\n", i, td->offsets[i], + (td->offsets[i] >> Tdiccshift) & Tdiccmask, + td->offsets[i] & 0x7FF); + return s; +} + +static void +dumptd(Td *td, char *p, int iso) +{ + static char buf[512]; /* Too much */ + char *s; + + s = seprint(buf, buf+sizeof(buf), "%s: ", p); + s = seprinttd(s, buf+sizeof(buf), td, iso); + if(s > buf && s[-1] != '\n') + s[-1] = '\n'; + print("\t%s", buf); +} + +static void +dumptds(Td *td, char *p, int iso) +{ + int i; + + for(i = 0; td != nil; td = td->next){ + dumptd(td, p, iso); + if(td->last) + break; + if(tdtok(td) == Tdtokin && ++i > 2){ + print("\t\t...\n"); + break; + } + } +} + +static void +dumped(Ed *ed) +{ + char *buf, *s, *e; + + if(ed == nil){ + print("\n"); + return; + } + buf = malloc(512); + /* no waserror; may want to use from interrupt context */ + if(buf == nil) + return; + e = buf+512; + s = seprint(buf, e, "\ted %#p: ctrl %#p", ed, ed->ctrl); + if((ed->ctrl & Edskip) != 0) + s = seprint(s, e, " skip"); + if((ed->ctrl & Ediso) != 0) + s = seprint(s, e, " iso"); + if((ed->ctrl & Edlow) != 0) + s = seprint(s, e, " low"); + s = seprint(s, e, " d%d", (ed->head & Edtoggle) ? 1 : 0); + if((ed->ctrl & Eddirmask) == Edin) + s = seprint(s, e, " in"); + if((ed->ctrl & Eddirmask) == Edout) + s = seprint(s, e, " out"); + if(edhalted(ed)) + s = seprint(s, e, " hlt"); + s = seprint(s, e, " ep%uld.%uld", (ed->ctrl>>7)&Epmax, ed->ctrl&0x7f); + s = seprint(s, e, " maxpkt %uld", (ed->ctrl>>Edmpsshift)&Edmpsmask); + seprint(s, e, " tail %#p head %#p next %#p\n",ed->tail,ed->head,ed->nexted); + print("%s", buf); + free(buf); + if(ed->tds != nil && (ed->ctrl & Ediso) == 0) + dumptds(ed->tds, "td", 0); +} + +static char* +seprintio(char *s, char *e, Qio *io, char *pref) +{ + s = seprint(s, e, "%s qio %#p ed %#p", pref, io, io->ed); + s = seprint(s, e, " tog %d iot %ld err %s id %#ulx", + io->toggle, io->iotime, io->err, io->usbid); + s = seprinttdtok(s, e, io->tok); + s = seprint(s, e, " %s\n", iosname[io->state]); + return s; +} + +static char* +seprintep(char* s, char* e, Ep *ep) +{ + Isoio *iso; + Qio *io; + Ctlio *cio; + + if(ep == nil) + return seprint(s, e, "\n"); + if(ep->aux == nil) + return seprint(s, e, "no mdep\n"); + switch(ep->ttype){ + case Tctl: + cio = ep->aux; + s = seprintio(s, e, cio, "c"); + s = seprint(s, e, "\trepl %d ndata %d\n", ep->rhrepl, cio->ndata); + break; + case Tbulk: + case Tintr: + io = ep->aux; + if(ep->mode != OWRITE) + s = seprintio(s, e, &io[OREAD], "r"); + if(ep->mode != OREAD) + s = seprintio(s, e, &io[OWRITE], "w"); + break; + case Tiso: + iso = ep->aux; + s = seprintio(s, e, iso, "w"); + s = seprint(s, e, "\tntds %d avail %d frno %uld left %uld next avail %#p\n", + iso->nframes, iso->navail, iso->frno, iso->left, iso->atds); + break; + } + return s; +} + +static char* +seprintctl(char *s, char *se, ulong ctl) +{ + s = seprint(s, se, "en="); + if((ctl&Cple) != 0) + s = seprint(s, se, "p"); + if((ctl&Cie) != 0) + s = seprint(s, se, "i"); + if((ctl&Ccle) != 0) + s = seprint(s, se, "c"); + if((ctl&Cble) != 0) + s = seprint(s, se, "b"); + switch(ctl & Cfsmask){ + case Cfsreset: + return seprint(s, se, " reset"); + case Cfsresume: + return seprint(s, se, " resume"); + case Cfsoper: + return seprint(s, se, " run"); + case Cfssuspend: + return seprint(s, se, " suspend"); + default: + return seprint(s, se, " ???"); + } +} + +static void +dump(Hci *hp) +{ + Ctlr *ctlr; + Ed *ed; + char cs[20]; + + ctlr = hp->aux; + ilock(ctlr); + seprintctl(cs, cs+sizeof(cs), ctlr->ohci->control); + print("ohci ctlr %#p: frno %#ux ctl %#lux %s sts %#lux intr %#lux\n", + ctlr, ctlr->hcca->framenumber, ctlr->ohci->control, cs, + ctlr->ohci->cmdsts, ctlr->ohci->intrsts); + print("ctlhd %#ulx cur %#ulx bulkhd %#ulx cur %#ulx done %#ulx\n", + ctlr->ohci->ctlheaded, ctlr->ohci->ctlcurred, + ctlr->ohci->bulkheaded, ctlr->ohci->bulkcurred, + ctlr->ohci->donehead); + if(ctlhd(ctlr) != nil) + print("[ctl]\n"); + for(ed = ctlhd(ctlr); ed != nil; ed = ed->next) + dumped(ed); + if(bulkhd(ctlr) != nil) + print("[bulk]\n"); + for(ed = bulkhd(ctlr); ed != nil; ed = ed->next) + dumped(ed); + if(ctlr->intrhd != nil) + print("[intr]\n"); + for(ed = ctlr->intrhd; ed != nil; ed = ed->inext) + dumped(ed); + if(ctlr->tree->root[0]->next != nil) + print("[iso]"); + for(ed = ctlr->tree->root[0]->next; ed != nil; ed = ed->next) + dumped(ed); + print("%d eds in tree\n", ctlr->ntree); + iunlock(ctlr); + lock(&tdpool); + print("%d tds allocated = %d in use + %d free\n", + tdpool.nalloc, tdpool.ninuse, tdpool.nfree); + unlock(&tdpool); + lock(&edpool); + print("%d eds allocated = %d in use + %d free\n", + edpool.nalloc, edpool.ninuse, edpool.nfree); + unlock(&edpool); +} + +/* + * Compute size for the next iso Td and setup its + * descriptor for I/O according to the buffer size. + */ +static void +isodtdinit(Ep *ep, Isoio *iso, Td *td) +{ + Block *bp; + long size; + int i; + + bp = td->bp; + assert(bp != nil && BLEN(bp) == 0); + size = (ep->hz+iso->left) * ep->pollival / 1000; + iso->left = (ep->hz+iso->left) * ep->pollival % 1000; + size *= ep->samplesz; + if(size > ep->maxpkt){ + print("ohci: ep%d.%d: size > maxpkt\n", + ep->dev->nb, ep->nb); + print("size = %uld max = %ld\n", size, ep->maxpkt); + size = ep->maxpkt; + } + td->nbytes = size; + memset(KSEG1ADDR(bp->wp), 0, size); /* in case we don't fill it on time */ + td->cbp0 = td->cbp = ptr2pa(bp->rp) & ~0xFFF; + td->ctrl = TRUNC(iso->frno, Ntdframes); + td->offsets[0] = (ptr2pa(bp->rp) & 0xFFF); + td->offsets[0] |= (Tdnotacc << Tdiccshift); + /* in case the controller checks out the offests... */ + for(i = 1; i < nelem(td->offsets); i++) + td->offsets[i] = td->offsets[0]; + td->be = ptr2pa(bp->rp + size - 1); + td->ctrl |= (0 << Tdfcshift); /* frame count is 1 */ + + iso->frno = TRUNC(iso->frno + ep->pollival, Ntdframes); +} + +/* + * start I/O on the dummy td and setup a new dummy to fill up. + */ +static void +isoadvance(Ep *ep, Isoio *iso, Td *td) +{ + Td *dtd; + + dtd = iso->atds; + iso->atds = dtd->anext; + iso->navail--; + dtd->anext = nil; + dtd->bp->wp = dtd->bp->rp; + dtd->nexttd = 0; + td->nexttd = ptr2pa(dtd); + isodtdinit(ep, iso, dtd); + iso->ed->tail = ptr2pa(dtd); +} + +static int +isocanwrite(void *a) +{ + Isoio *iso; + + iso = a; + return iso->state == Qclose || iso->err != nil || + iso->navail > iso->nframes / 2; +} + +/* + * Service a completed/failed Td from the done queue. + * It may be of any transfer type. + * The queue is not in completion order. + * (It's actually in reverse completion order). + * + * When an error, a short packet, or a last Td is found + * we awake the process waiting for the transfer. + * Although later we will process other Tds completed + * before, epio won't be able to touch the current Td + * until interrupt returns and releases the lock on the + * controller. + */ +static void +qhinterrupt(Ctlr *, Ep *ep, Qio *io, Td *td, int) +{ + Block *bp; + int mode, err; + Ed *ed; + + ed = io->ed; + if(io->state != Qrun) + return; + if(tdtok(td) == Tdtokin) + mode = OREAD; + else + mode = OWRITE; + bp = td->bp; + err = tderrs(td); + + switch(err){ + case Tddataovr: /* Overrun is not an error */ + break; + case Tdok: + /* virtualbox doesn't always report underflow on short packets */ + if(td->cbp == 0) + break; + /* fall through */ + case Tddataund: + /* short input packets are ok */ + if(mode == OREAD){ + if(td->cbp == 0) + panic("ohci: short packet but cbp == 0"); + /* + * td->cbp and td->cbp0 are the real addresses + * corresponding to virtual addresses bp->wp and + * bp->rp respectively. + */ + bp->wp = bp->rp + (td->cbp - td->cbp0); + if(bp->wp < bp->rp) + panic("ohci: wp < rp"); + /* + * It's ok. clear error and flag as last in xfer. + * epio must ignore following Tds. + */ + td->last = 1; + td->ctrl &= ~(Tdccmask << Tdccshift); + break; + } + /* else fall; it's an error */ + case Tdcrc: + case Tdbitstuff: + case Tdbadtog: + case Tdstalled: + case Tdtmout: + case Tdpidchk: + case Tdbadpid: + bp->wp = bp->rp; /* no bytes in xfer. */ + io->err = errmsg(err); + if(debug || ep->debug){ + print("tdinterrupt: failed err %d (%s)\n", err, io->err); + dumptd(td, "failed", ed->ctrl & Ediso); + } + td->last = 1; + break; + default: + panic("ohci: td cc %ud unknown", err); + } + + if(td->last != 0){ + /* + * clear td list and halt flag. + */ + ed->head = (ed->head & Edtoggle) | ed->tail; + ed->tds = pa2ptr(ed->tail); + io->state = Qdone; + wakeup(io); + } +} + +/* + * BUG: Iso input streams are not implemented. + */ +static void +isointerrupt(Ctlr *ctlr, Ep *ep, Qio *io, Td *td, int) +{ + Isoio *iso; + Block *bp; + Ed *ed; + int err, isoerr; + + iso = ep->aux; + ed = io->ed; + if(io->state == Qclose) + return; + bp = td->bp; + /* + * When we get more than half the frames consecutive errors + * we signal an actual error. Errors in the entire Td are + * more serious and are always singaled. + * Errors like overrun are not really errors. In fact, for + * output, errors cannot be really detected. The driver will + * hopefully notice I/O errors on input endpoints and detach the device. + */ + err = tderrs(td); + isoerr = (td->offsets[0] >> Tdiccshift) & Tdiccmask; + if(isoerr == Tdok || isoerr == Tdnotacc) + iso->nerrs = 0; + else if(iso->nerrs++ > iso->nframes/2) + err = Tdstalled; + if(err != Tdok && err != Tddataovr){ + bp->wp = bp->rp; + io->err = errmsg(err); + if(debug || ep->debug){ + print("ohci: isointerrupt: ep%d.%d: err %d (%s) frnum 0x%lux\n", + ep->dev->nb, ep->nb, + err, errmsg(err), ctlr->ohci->fmnumber); + dumptd(td, "failed", ed->ctrl & Ediso); + } + } + td->bp->wp = td->bp->rp; + td->nbytes = 0; + td->anext = iso->atds; + iso->atds = td; + iso->navail++; + /* + * If almost all Tds are avail the user is not doing I/O at the + * required rate. We put another Td in place to keep the polling rate. + */ + if(iso->err == nil && iso->navail > iso->nframes - 10) + isoadvance(ep, iso, pa2ptr(iso->ed->tail)); + /* + * If there's enough buffering futher I/O can be done. + */ + if(isocanwrite(iso)) + wakeup(iso); +} + +static void +interrupt(Ureg *, void *arg) +{ + Td *td, *ntd; + Hci *hp; + Ctlr *ctlr; + ulong status, curred; + int i, frno; + + hp = arg; + ctlr = hp->aux; + ilock(ctlr); + ctlr->ohci->intrdisable = Mie; + coherence(); + status = ctlr->ohci->intrsts & ctlr->ohci->intrenable; + status &= Oc|Rhsc|Fno|Ue|Rd|Sf|Wdh|So; + frno = TRUNC(ctlr->ohci->fmnumber, Ntdframes); + if(status & Wdh){ + /* lsb of donehead has bit to flag other intrs. */ + td = pa2ptr(ctlr->hcca->donehead & ~0xF); + + for(i = 0; td != nil && i < 1024; i++){ +// if(0)ddprint("ohci tdinterrupt: td %#p\n", td); + ntd = pa2ptr(td->nexttd & ~0xF); + td->nexttd = 0; + if(td->ep == nil || td->io == nil) + panic("ohci: interrupt: ep %#p io %#p", + td->ep, td->io); + ohciinterrupts[td->ep->ttype]++; + if(td->ep->ttype == Tiso) + isointerrupt(ctlr, td->ep, td->io, td, frno); + else + qhinterrupt(ctlr, td->ep, td->io, td, frno); + td = ntd; + } + if(i >= 1024) + print("ohci: bug: more than 1024 done Tds?\n"); + ctlr->hcca->donehead = 0; + } + + ctlr->ohci->intrsts = status; + status &= ~Wdh; + status &= ~Sf; + if(status & So){ + print("ohci: sched overrun: too much load\n"); + ctlr->overrun++; + status &= ~So; + } + if((status & Ue) != 0){ + curred = ctlr->ohci->periodcurred; + print("ohci: unrecoverable error frame 0x%.8lux ed 0x%.8lux, " + "ints %d %d %d %d\n", + ctlr->ohci->fmnumber, curred, + ohciinterrupts[Tctl], ohciinterrupts[Tintr], + ohciinterrupts[Tbulk], ohciinterrupts[Tiso]); + if(curred != 0) + dumped(pa2ptr(curred)); + status &= ~Ue; + } + if(status != 0) + print("ohci interrupt: unhandled sts 0x%.8lux\n", status); + ctlr->ohci->intrenable = Mie | Wdh | Ue; + iunlock(ctlr); +} + +/* + * The old dummy Td is used to implement the new Td. + * A new dummy is linked at the end of the old one and + * returned, to link further Tds if needed. + */ +static Td* +epgettd(Ep *ep, Qio *io, Td **dtdp, int flags, void *a, int count) +{ + Td *td, *dtd; + Block *bp; + + if(count <= BY2PG) + bp = allocb(count); + else{ + if(count > 2*BY2PG) + panic("ohci: transfer > two pages"); + /* maximum of one physical page crossing allowed */ + bp = allocb(count+BY2PG); + bp->rp = (uchar*)PGROUND((uintptr)bp->rp); + bp->wp = bp->rp; + } + dtd = *dtdp; + td = dtd; + td->bp = bp; + if(count > 0){ + td->cbp0 = td->cbp = ptr2pa(bp->wp); + td->be = ptr2pa(bp->wp + count - 1); + if(a != nil){ + /* validaddr((uintptr)a, count, 0); DEBUG */ + memmove(KSEG1ADDR(bp->wp), a, count); + } + bp->wp += count; + } + td->nbytes = count; + td->ctrl = io->tok|Tdusetog|io->toggle|flags; + if(io->toggle == Tddata0) + io->toggle = Tddata1; + else + io->toggle = Tddata0; + assert(td->ep == ep); + td->io = io; + dtd = tdalloc(); /* new dummy */ + dtd->ep = ep; + td->nexttd = ptr2pa(dtd); + td->next = dtd; + *dtdp = dtd; + return td; +} + +/* + * Try to get them idle + */ +static void +aborttds(Qio *io) +{ + Ed *ed; + Td *td; + + ed = io->ed; + if(ed == nil) + return; + ed->ctrl |= Edskip; + for(td = ed->tds; td != nil; td = td->next) + if(td->bp != nil) + td->bp->wp = td->bp->rp; + ed->head = (ed->head&0xF) | ed->tail; + if((ed->ctrl & Ediso) == 0) + ed->tds = pa2ptr(ed->tail); +} + +static int +epiodone(void *a) +{ + Qio *io; + + io = a; + return io->state != Qrun; +} + +static void +epiowait(Ctlr *ctlr, Qio *io, int tmout, ulong) +{ + Ed *ed; + int timedout; + + ed = io->ed; +// if(0)ddqprint("ohci io %#p sleep on ed %#p state %s\n", +// io, ed, iosname[io->state]); + timedout = 0; + if(waserror()){ + dqprint("ohci io %#p ed %#p timed out\n", io, ed); + timedout++; + }else{ + if(tmout == 0) + sleep(io, epiodone, io); + else + tsleep(io, epiodone, io, tmout); + poperror(); + } + ilock(ctlr); + if(io->state == Qrun) + timedout = 1; + else if(io->state != Qdone && io->state != Qclose) + panic("epio: ed not done and not closed"); + if(timedout){ + aborttds(io); + io->err = "request timed out"; + iunlock(ctlr); + if(!waserror()){ + tsleep(&up->sleep, return0, 0, Abortdelay); + poperror(); + } + ilock(ctlr); + } + if(io->state != Qclose) + io->state = Qidle; + iunlock(ctlr); +} + +/* + * Non iso I/O. + * To make it work for control transfers, the caller may + * lock the Qio for the entire control transfer. + */ +static long +epio(Ep *ep, Qio *io, void *a, long count, int mustlock) +{ + Ed *ed; + Ctlr *ctlr; + char buf[80]; + char *err; + uchar *c; + Td *td, *ltd, *ntd, *td0; + int last, ntds, tmout; + long tot, n; + ulong load; + + ed = io->ed; + ctlr = ep->hp->aux; + io->debug = ep->debug; + tmout = ep->tmout; + ddeprint("ohci: %s ep%d.%d io %#p count %ld\n", + io->tok == Tdtokin ? "in" : "out", + ep->dev->nb, ep->nb, io, count); + if((debug > 1 || ep->debug > 1) && io->tok != Tdtokin){ + seprintdata(buf, buf+sizeof(buf), a, count); + print("\t%s\n", buf); + } + if(mustlock){ + qlock(io); + if(waserror()){ + qunlock(io); + nexterror(); + } + } + io->err = nil; + ilock(ctlr); + if(io->state == Qclose){ /* Tds released by cancelio */ + iunlock(ctlr); + error(io->err ? io->err : Eio); + } + if(io->state != Qidle) + panic("epio: qio not idle"); + io->state = Qinstall; + + c = a; + ltd = td0 = ed->tds; + load = tot = 0; + do{ + n = 2*BY2PG; + if(count-tot < n) + n = count-tot; + if(c != nil && io->tok != Tdtokin) + td = epgettd(ep, io, <d, 0, c+tot, n); + else + td = epgettd(ep, io, <d, 0, nil, n); + tot += n; + load += ep->load; + }while(tot < count); + if(td0 == nil || ltd == nil || td0 == ltd) + panic("epio: no td"); + td->last = 1; + if(debug > 2 || ep->debug > 2) + dumptds(td0, "put td", ep->ttype == Tiso); + iunlock(ctlr); + + ilock(ctlr); + if(io->state != Qclose){ + io->iotime = TK2MS(MACHP(0)->ticks); + io->state = Qrun; + ed->tail = ptr2pa(ltd); + if(ep->ttype == Tctl) + ctlr->ohci->cmdsts |= Sclf; + else if(ep->ttype == Tbulk) + ctlr->ohci->cmdsts |= Sblf; + } + iunlock(ctlr); + + epiowait(ctlr, io, tmout, load); + ilock(ctlr); + if(debug > 1 || ep->debug > 1) + dumptds(td0, "got td", 0); + iunlock(ctlr); + + tot = 0; + c = a; + ntds = last = 0; + for(td = td0; td != ltd; td = ntd){ + ntds++; + /* + * If the Td is flagged as last we must + * ignore any following Td. The block may + * seem to have bytes but interrupt has not seen + * those Tds through the done queue, and they are void. + */ + if(last == 0 && tderrs(td) == Tdok){ + n = BLEN(td->bp); + tot += n; + if(c != nil && tdtok(td) == Tdtokin && n > 0){ + memmove(c, KSEG1ADDR(td->bp->rp), n); + c += n; + } + } + last |= td->last; + ntd = td->next; + tdfree(td); + } + if(edtoggle(ed) == 0) + io->toggle = Tddata0; + else + io->toggle = Tddata1; + + err = io->err; + if(mustlock){ + qunlock(io); + poperror(); + } + ddeprint("ohci: io %#p: %d tds: return %ld err '%s'\n\n", + io, ntds, tot, err); + if(err != nil) + error(err); + if(tot < 0) + error(Eio); + return tot; +} + +/* + * halt condition was cleared on the endpoint. update our toggles. + */ +static void +clrhalt(Ep *ep) +{ + Qio *io; + + ep->clrhalt = 0; + switch(ep->ttype){ + case Tbulk: + case Tintr: + io = ep->aux; + if(ep->mode != OREAD){ + qlock(&io[OWRITE]); + io[OWRITE].toggle = Tddata0; + deprint("ep clrhalt for io %#p\n", io+OWRITE); + qunlock(&io[OWRITE]); + } + if(ep->mode != OWRITE){ + qlock(&io[OREAD]); + io[OREAD].toggle = Tddata0; + deprint("ep clrhalt for io %#p\n", io+OREAD); + qunlock(&io[OREAD]); + } + break; + } +} + +static long +epread(Ep *ep, void *a, long count) +{ + Ctlio *cio; + Qio *io; + char buf[80]; + ulong delta; + + if(ep->aux == nil) + panic("epread: not open"); + + switch(ep->ttype){ + case Tctl: + cio = ep->aux; + qlock(cio); + if(waserror()){ + qunlock(cio); + nexterror(); + } + ddeprint("epread ctl ndata %d\n", cio->ndata); + if(cio->ndata < 0) + error("request expected"); + else if(cio->ndata == 0){ + cio->ndata = -1; + count = 0; + }else{ + if(count > cio->ndata) + count = cio->ndata; + if(count > 0) + memmove(a, KSEG1ADDR(cio->data), count); + /* BUG for big transfers */ + free(cio->data); + cio->data = nil; + cio->ndata = 0; /* signal EOF next time */ + } + qunlock(cio); + poperror(); + if(debug>1 || ep->debug){ + seprintdata(buf, buf+sizeof(buf), a, count); + print("epread: %s\n", buf); + } + return count; + case Tbulk: + io = ep->aux; + if(ep->clrhalt) + clrhalt(ep); + return epio(ep, &io[OREAD], a, count, 1); + case Tintr: + io = ep->aux; + delta = TK2MS(MACHP(0)->ticks) - io[OREAD].iotime + 1; + if(delta < ep->pollival / 2) + tsleep(&up->sleep, return0, 0, ep->pollival/2 - delta); + if(ep->clrhalt) + clrhalt(ep); + return epio(ep, &io[OREAD], a, count, 1); + case Tiso: + panic("ohci: iso read not implemented"); + break; + default: + panic("epread: bad ep ttype %d", ep->ttype); + } + return -1; +} + +/* + * Control transfers are one setup write (data0) + * plus zero or more reads/writes (data1, data0, ...) + * plus a final write/read with data1 to ack. + * For both host to device and device to host we perform + * the entire transfer when the user writes the request, + * and keep any data read from the device for a later read. + * We call epio three times instead of placing all Tds at + * the same time because doing so leads to crc/tmout errors + * for some devices. + * Upon errors on the data phase we must still run the status + * phase or the device may cease responding in the future. + */ +static long +epctlio(Ep *ep, Ctlio *cio, void *a, long count) +{ + uchar *c; + long len; + + ddeprint("epctlio: cio %#p ep%d.%d count %ld\n", + cio, ep->dev->nb, ep->nb, count); + if(count < Rsetuplen) + error("short usb command"); + qlock(cio); + free(cio->data); + cio->data = nil; + cio->ndata = 0; + if(waserror()){ + qunlock(cio); + free(cio->data); + cio->data = nil; + cio->ndata = 0; + nexterror(); + } + + /* set the address if unset and out of configuration state */ + if(ep->dev->state != Dconfig && ep->dev->state != Dreset) + if(cio->usbid == 0){ + cio->usbid = (ep->nb<<7)|(ep->dev->nb & Devmax); + edsetaddr(cio->ed, cio->usbid); + } + /* adjust maxpkt if the user has learned a different one */ + if(edmaxpkt(cio->ed) != ep->maxpkt) + edsetmaxpkt(cio->ed, ep->maxpkt); + c = a; + cio->tok = Tdtoksetup; + cio->toggle = Tddata0; + if(epio(ep, cio, a, Rsetuplen, 0) < Rsetuplen) + error(Eio); + + a = c + Rsetuplen; + count -= Rsetuplen; + + cio->toggle = Tddata1; + if(c[Rtype] & Rd2h){ + cio->tok = Tdtokin; + len = GET2(c+Rcount); + if(len <= 0) + error("bad length in d2h request"); + if(len > Maxctllen) + error("d2h data too large to fit in ohci"); + a = cio->data = smalloc(len+1); + }else{ + cio->tok = Tdtokout; + len = count; + } + if(len > 0) + if(waserror()) + len = -1; + else{ + len = epio(ep, cio, a, len, 0); + poperror(); + } + if(c[Rtype] & Rd2h){ + count = Rsetuplen; + cio->ndata = len; + cio->tok = Tdtokout; + }else{ + if(len < 0) + count = -1; + else + count = Rsetuplen + len; + cio->tok = Tdtokin; + } + cio->toggle = Tddata1; + epio(ep, cio, nil, 0, 0); + qunlock(cio); + poperror(); + ddeprint("epctlio cio %#p return %ld\n", cio, count); + return count; +} + +/* + * Put new samples in the dummy Td. + * BUG: This does only a transfer per Td. We could do up to 8. + */ +static long +putsamples(Ctlr *ctlr, Ep *ep, Isoio *iso, uchar *b, long count) +{ + Td *td; + ulong n; + + td = pa2ptr(iso->ed->tail); + n = count; + if(n > td->nbytes - BLEN(td->bp)) + n = td->nbytes - BLEN(td->bp); + assert(td->bp->wp + n <= td->bp->lim); + memmove(KSEG1ADDR(td->bp->wp), b, n); + td->bp->wp += n; + if(BLEN(td->bp) == td->nbytes){ /* full Td: activate it */ + ilock(ctlr); + isoadvance(ep, iso, td); + iunlock(ctlr); + } + return n; +} + +static long +episowrite(Ep *ep, void *a, long count) +{ + long tot, nw; + char *err; + uchar *b; + Ctlr *ctlr; + Isoio *iso; + + ctlr = ep->hp->aux; + iso = ep->aux; + iso->debug = ep->debug; + + qlock(iso); + if(waserror()){ + qunlock(iso); + nexterror(); + } + diprint("ohci: episowrite: %#p ep%d.%d\n", iso, ep->dev->nb, ep->nb); + ilock(ctlr); + if(iso->state == Qclose){ + iunlock(ctlr); + error(iso->err ? iso->err : Eio); + } + iso->state = Qrun; + b = a; + for(tot = 0; tot < count; tot += nw){ + while(isocanwrite(iso) == 0){ + iunlock(ctlr); + diprint("ohci: episowrite: %#p sleep\n", iso); + if(waserror()){ + if(iso->err == nil) + iso->err = "I/O timed out"; + ilock(ctlr); + break; + } + tsleep(iso, isocanwrite, iso, ep->tmout); + poperror(); + ilock(ctlr); + } + err = iso->err; + iso->err = nil; + if(iso->state == Qclose || err != nil){ + iunlock(ctlr); + error(err ? err : Eio); + } + if(iso->state != Qrun) + panic("episowrite: iso not running"); + iunlock(ctlr); /* We could page fault here */ + nw = putsamples(ctlr, ep, iso, b+tot, count-tot); + ilock(ctlr); + } + if(iso->state != Qclose) + iso->state = Qdone; + iunlock(ctlr); + err = iso->err; /* in case it failed early */ + iso->err = nil; + qunlock(iso); + poperror(); + if(err != nil) + error(err); + diprint("ohci: episowrite: %#p %ld bytes\n", iso, tot); + return tot; +} + +static long +epwrite(Ep *ep, void *a, long count) +{ + Qio *io; + Ctlio *cio; + ulong delta; + uchar *b; + long tot, nw; + + if(ep->aux == nil) + panic("ohci: epwrite: not open"); + switch(ep->ttype){ + case Tctl: + cio = ep->aux; + return epctlio(ep, cio, a, count); + case Tbulk: + io = ep->aux; + if(ep->clrhalt) + clrhalt(ep); + /* + * Put at most Tdatomic Tds (512 bytes) at a time. + * Otherwise some devices produce babble errors. + */ + b = a; + assert(a != nil); + for(tot = 0; tot < count ; tot += nw){ + nw = count - tot; + if(nw > Tdatomic * ep->maxpkt) + nw = Tdatomic * ep->maxpkt; + nw = epio(ep, &io[OWRITE], b+tot, nw, 1); + } + return tot; + case Tintr: + io = ep->aux; + delta = TK2MS(MACHP(0)->ticks) - io[OWRITE].iotime + 1; + if(delta < ep->pollival) + tsleep(&up->sleep, return0, 0, ep->pollival - delta); + if(ep->clrhalt) + clrhalt(ep); + return epio(ep, &io[OWRITE], a, count, 1); + case Tiso: + return episowrite(ep, a, count); + default: + panic("ohci: epwrite: bad ep ttype %d", ep->ttype); + } + return -1; +} + +static Ed* +newed(Ctlr *ctlr, Ep *ep, Qio *io, char *) +{ + Ed *ed; + Td *td; + + ed = io->ed = edalloc(); /* no errors raised here, really */ + td = tdalloc(); + td->ep = ep; + td->io = io; + ed->tail = ptr2pa(td); + ed->head = ptr2pa(td); + ed->tds = td; + ed->ep = ep; + ed->ctrl = (ep->maxpkt & Edmpsmask) << Edmpsshift; + if(ep->ttype == Tiso) + ed->ctrl |= Ediso; + if(waserror()){ + edfree(ed); + io->ed = nil; + nexterror(); + } + /* For setup endpoints we start with the config address */ + if(ep->ttype != Tctl) + edsetaddr(io->ed, io->usbid); + if(ep->dev->speed == Lowspeed) + ed->ctrl |= Edlow; + switch(io->tok){ + case Tdtokin: + ed->ctrl |= Edin; + break; + case Tdtokout: + ed->ctrl |= Edout; + break; + default: + ed->ctrl |= Edtddir; /* Td will say */ + break; + } + + switch(ep->ttype){ + case Tctl: + ilock(ctlr); + edlinked(ed, ctlhd(ctlr)); + setctlhd(ctlr, ed); + iunlock(ctlr); + break; + case Tbulk: + ilock(ctlr); + edlinked(ed, bulkhd(ctlr)); + setbulkhd(ctlr, ed); + iunlock(ctlr); + break; + case Tintr: + case Tiso: + ilock(ctlr); + schedq(ctlr, io, ep->pollival); + iunlock(ctlr); + break; + default: + panic("ohci: newed: bad ttype"); + } + poperror(); + return ed; +} + +static void +isoopen(Ctlr *ctlr, Ep *ep) +{ + Td *td, *edtds; + Isoio *iso; + int i; + + iso = ep->aux; + iso->usbid = (ep->nb<<7)|(ep->dev->nb & Devmax); + iso->bw = ep->hz * ep->samplesz; /* bytes/sec */ + if(ep->mode != OWRITE){ + print("ohci: bug: iso input streams not implemented\n"); + error("ohci iso input streams not implemented"); + }else + iso->tok = Tdtokout; + + iso->left = 0; + iso->nerrs = 0; + iso->frno = TRUNC(ctlr->ohci->fmnumber + 10, Ntdframes); + iso->nframes = 1000 / ep->pollival; + if(iso->nframes < 10){ + print("ohci: isoopen: less than 10 frames; using 10.\n"); + iso->nframes = 10; + } + iso->navail = iso->nframes; + iso->atds = edtds = nil; + for(i = 0; i < iso->nframes-1; i++){ /* -1 for dummy */ + td = tdalloc(); + td->ep = ep; + td->io = iso; + td->bp = allocb(ep->maxpkt); + td->anext = iso->atds; /* link as avail */ + iso->atds = td; + td->next = edtds; + edtds = td; + } + newed(ctlr, ep, iso, "iso"); /* allocates a dummy td */ + iso->ed->tds->bp = allocb(ep->maxpkt); /* but not its block */ + iso->ed->tds->next = edtds; + isodtdinit(ep, iso, iso->ed->tds); +} + +/* + * Allocate the endpoint and set it up for I/O + * in the controller. This must follow what's said + * in Ep regarding configuration, including perhaps + * the saved toggles (saved on a previous close of + * the endpoint data file by epclose). + */ +static void +epopen(Ep *ep) +{ + Ctlr *ctlr; + Qio *io; + Ctlio *cio; + ulong usbid; + + ctlr = ep->hp->aux; + deprint("ohci: epopen ep%d.%d\n", ep->dev->nb, ep->nb); + if(ep->aux != nil) + panic("ohci: epopen called with open ep"); + if(waserror()){ + free(ep->aux); + ep->aux = nil; + nexterror(); + } + switch(ep->ttype){ + case Tnone: + error("endpoint not configured"); + case Tiso: + ep->aux = smalloc(sizeof(Isoio)); + isoopen(ctlr, ep); + break; + case Tctl: + cio = ep->aux = smalloc(sizeof(Ctlio)); + cio->debug = ep->debug; + cio->ndata = -1; + cio->data = nil; + cio->tok = -1; /* invalid; Tds will say */ + if(ep->dev->isroot != 0 && ep->nb == 0) /* root hub */ + break; + newed(ctlr, ep, cio, "epc"); + break; + case Tbulk: + ep->pollival = 1; /* assume this; doesn't really matter */ + /* and fall... */ + case Tintr: + io = ep->aux = smalloc(sizeof(Qio)*2); + io[OREAD].debug = io[OWRITE].debug = ep->debug; + usbid = (ep->nb<<7)|(ep->dev->nb & Devmax); + if(ep->mode != OREAD){ + if(ep->toggle[OWRITE] != 0) + io[OWRITE].toggle = Tddata1; + else + io[OWRITE].toggle = Tddata0; + io[OWRITE].tok = Tdtokout; + io[OWRITE].usbid = usbid; + io[OWRITE].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */ + newed(ctlr, ep, io+OWRITE, "epw"); + } + if(ep->mode != OWRITE){ + if(ep->toggle[OREAD] != 0) + io[OREAD].toggle = Tddata1; + else + io[OREAD].toggle = Tddata0; + io[OREAD].tok = Tdtokin; + io[OREAD].usbid = usbid; + io[OREAD].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */ + newed(ctlr, ep, io+OREAD, "epr"); + } + break; + } + deprint("ohci: epopen done:\n"); + if(debug || ep->debug) + dump(ep->hp); + poperror(); +} + +static void +cancelio(Ep *ep, Qio *io) +{ + Ed *ed; + Ctlr *ctlr; + + ctlr = ep->hp->aux; + + ilock(ctlr); + if(io == nil || io->state == Qclose){ + assert(io == nil || io->ed == nil); + iunlock(ctlr); + return; + } + ed = io->ed; + io->state = Qclose; + io->err = Eio; + aborttds(io); + iunlock(ctlr); + if(!waserror()){ + tsleep(&up->sleep, return0, 0, Abortdelay); + poperror(); + } + + wakeup(io); + qlock(io); + /* wait for epio if running */ + qunlock(io); + + ilock(ctlr); + switch(ep->ttype){ + case Tctl: + unlinkctl(ctlr, ed); + break; + case Tbulk: + unlinkbulk(ctlr, ed); + break; + case Tintr: + case Tiso: + unschedq(ctlr, io); + break; + default: + panic("ohci cancelio: bad ttype"); + } + iunlock(ctlr); + edfree(io->ed); + io->ed = nil; +} + +static void +epclose(Ep *ep) +{ + Ctlio *cio; + Isoio *iso; + Qio *io; + + deprint("ohci: epclose ep%d.%d\n", ep->dev->nb, ep->nb); + if(ep->aux == nil) + panic("ohci: epclose called with closed ep"); + switch(ep->ttype){ + case Tctl: + cio = ep->aux; + cancelio(ep, cio); + free(cio->data); + cio->data = nil; + break; + case Tbulk: + case Tintr: + io = ep->aux; + if(ep->mode != OWRITE){ + cancelio(ep, &io[OREAD]); + if(io[OREAD].toggle == Tddata1) + ep->toggle[OREAD] = 1; + } + if(ep->mode != OREAD){ + cancelio(ep, &io[OWRITE]); + if(io[OWRITE].toggle == Tddata1) + ep->toggle[OWRITE] = 1; + } + break; + case Tiso: + iso = ep->aux; + cancelio(ep, iso); + break; + default: + panic("epclose: bad ttype %d", ep->ttype); + } + + deprint("ohci: epclose ep%d.%d: done\n", ep->dev->nb, ep->nb); + free(ep->aux); + ep->aux = nil; +} + +static int +portreset(Hci *hp, int port, int on) +{ + Ctlr *ctlr; + Ohci *ohci; + + if(on == 0) + return 0; + + ctlr = hp->aux; + qlock(&ctlr->resetl); + if(waserror()){ + qunlock(&ctlr->resetl); + nexterror(); + } + ilock(ctlr); + ohci = ctlr->ohci; + ohci->rhportsts[port - 1] = Spp; + if((ohci->rhportsts[port - 1] & Ccs) == 0){ + iunlock(ctlr); + error("port not connected"); + } + ohci->rhportsts[port - 1] = Spr; + while((ohci->rhportsts[port - 1] & Prsc) == 0){ + iunlock(ctlr); + dprint("ohci: portreset, wait for reset complete\n"); + ilock(ctlr); + } + ohci->rhportsts[port - 1] = Prsc; + iunlock(ctlr); + poperror(); + qunlock(&ctlr->resetl); + return 0; +} + +static int +portenable(Hci *hp, int port, int on) +{ + Ctlr *ctlr; + + ctlr = hp->aux; + dprint("ohci: %#p port %d enable=%d\n", ctlr->ohci, port, on); + qlock(&ctlr->resetl); + if(waserror()){ + qunlock(&ctlr->resetl); + nexterror(); + } + ilock(ctlr); + if(on) + ctlr->ohci->rhportsts[port - 1] = Spe | Spp; + else + ctlr->ohci->rhportsts[port - 1] = Cpe; + iunlock(ctlr); + tsleep(&up->sleep, return0, 0, Enabledelay); + poperror(); + qunlock(&ctlr->resetl); + return 0; +} + +static int +portstatus(Hci *hp, int port) +{ + int v; + Ctlr *ub; + ulong ohcistatus; + + /* + * We must return status bits as a + * get port status hub request would do. + */ + ub = hp->aux; + ohcistatus = ub->ohci->rhportsts[port - 1]; + v = 0; + if(ohcistatus & Ccs) + v |= HPpresent; + if(ohcistatus & Pes) + v |= HPenable; + if(ohcistatus & Pss) + v |= HPsuspend; + if(ohcistatus & Prs) + v |= HPreset; + else { + /* port is not in reset; these potential writes are ok */ + if(ohcistatus & Csc){ + v |= HPstatuschg; + ub->ohci->rhportsts[port - 1] = Csc; + } + if(ohcistatus & Pesc){ + v |= HPchange; + ub->ohci->rhportsts[port - 1] = Pesc; + } + } + if(ohcistatus & Lsda) + v |= HPslow; + if(v & (HPstatuschg|HPchange)) + ddprint("ohci port %d sts %#ulx hub sts %#x\n", port, ohcistatus, v); + return v; +} + +static void +dumpohci(Ctlr *ctlr) +{ + int i; + ulong *ohci; + + ohci = &ctlr->ohci->revision; + print("ohci registers: \n"); + for(i = 0; i < sizeof(Ohci)/sizeof(ulong); i++) + if(i < 3 || ohci[i] != 0) + print("\t[%#2.2x]\t%#8.8ulx\n", i * 4, ohci[i]); + print("\n"); +} + +static void +init(Hci *hp) +{ + Ctlr *ctlr; + Ohci *ohci; + int i; + ulong ival, ctrl, fmi; + + ctlr = hp->aux; + dprint("ohci %#p init\n", ctlr->ohci); + ohci = ctlr->ohci; + + fmi = ctlr->ohci->fminterval; + ctlr->ohci->cmdsts = Shcr; /* reset the block */ + while(ctlr->ohci->cmdsts & Shcr) + delay(1); /* wait till reset complete, Ohci says 10us max. */ + ctlr->ohci->fminterval = fmi; + + /* + * now that soft reset is done we are in suspend state. + * Setup registers which take in suspend state + * (will only be here for 2ms). + */ + + ctlr->ohci->hcca = ptr2pa(ctlr->hcca); + setctlhd(ctlr, nil); + ctlr->ohci->ctlcurred = 0; + setbulkhd(ctlr, nil); + ctlr->ohci->bulkcurred = 0; + + ohci->intrenable = Mie | Wdh | Ue; + ohci->control |= Ccle | Cble | Cple | Cie | Cfsoper; + + /* set frame after operational */ + ohci->rhdesca = Nps; /* no power switching */ + if(ohci->rhdesca & Nps){ + dprint("ohci: ports are not power switched\n"); + }else{ + dprint("ohci: ports are power switched\n"); + ohci->rhdesca &= ~Psm; + ohci->rhsts &= ~Lpsc; + } + for(i = 0; i < ctlr->nports; i++) /* paranoia */ + ohci->rhportsts[i] = 0; /* this has no effect */ + delay(50); + + for(i = 0; i < ctlr->nports; i++){ + ohci->rhportsts[i] = Spp; + if((ohci->rhportsts[i] & Ccs) != 0) + ohci->rhportsts[i] |= Spr; + } + delay(100); + + ctrl = ohci->control; + if((ctrl & Cfsmask) != Cfsoper){ + ctrl = (ctrl & ~Cfsmask) | Cfsoper; + ohci->control = ctrl; + ohci->rhsts = Lpsc; + } + ival = ohci->fminterval & ~(Fmaxpktmask << Fmaxpktshift); + ohci->fminterval = ival | (5120 << Fmaxpktshift); + + if(debug > 1) + dumpohci(ctlr); +} + +static void +scanpci(void) +{ + ulong mem; + Ctlr *ctlr; + Pcidev *p; + int i; + static int already = 0; + + if(already) + return; + already = 1; + p = nil; + while(p = pcimatch(p, 0, 0)) { + /* + * Find Ohci controllers (Programming Interface = 0x10). + */ + if(p->ccrb != Pcibcserial || p->ccru != Pciscusb || + p->ccrp != 0x10) + continue; + mem = p->mem[0].bar & ~0x0F; + dprint("ohci: %x/%x port 0x%lux size 0x%x irq %d\n", + p->vid, p->did, mem, p->mem[0].size, p->intl); + if(mem == 0){ + print("ohci: failed to map registers\n"); + continue; + } +// if(p->intl == 0xFF || p->intl == 0) { +// print("ohci: no irq assigned for port %#lux\n", mem); +// continue; +// } + + ctlr = malloc(sizeof(Ctlr)); + if (ctlr == nil) + panic("ohci: out of memory"); + ctlr->pcidev = p; + ctlr->ohci = KSEG1ADDR(PCIMEMADDR(mem)); + dprint("scanpci: ctlr %#p, ohci %#p\n", ctlr, ctlr->ohci); + pcisetbme(p); + pcisetpms(p, 0); + for(i = 0; i < Nhcis; i++) + if(ctlrs[i] == nil){ + ctlrs[i] = ctlr; + break; + } + if(i == Nhcis) + print("ohci: bug: no more controllers\n"); + } +} + +static void +usbdebug(Hci*, int d) +{ + debug = d; +} + +/* + * build the periodic scheduling tree: + * framesize must be a multiple of the tree size + */ +static void +mkqhtree(Ctlr *ctlr) +{ + int i, n, d, o, leaf0, depth; + Ed **tree; + Qtree *qt; + + depth = flog2(32); + n = (1 << (depth+1)) - 1; + qt = mallocz(sizeof(*qt), 1); + if(qt == nil) + panic("usb: can't allocate scheduling tree"); + qt->nel = n; + qt->depth = depth; + qt->bw = mallocz(n * sizeof(qt->bw), 1); + qt->root = tree = mallocz(n * sizeof(Ed *), 1); + if(qt->bw == nil || qt->root == nil) + panic("usb: can't allocate scheduling tree"); + for(i = 0; i < n; i++){ + if((tree[i] = edalloc()) == nil) + panic("mkqhtree"); + tree[i]->ctrl = (8 << Edmpsshift); /* not needed */ + tree[i]->ctrl |= Edskip; + + if(i > 0) + edlinked(tree[i], tree[(i-1)/2]); + else + edlinked(tree[i], nil); + } + ctlr->ntree = i; + dprint("ohci: tree: %d endpoints allocated\n", i); + + /* distribute leaves evenly round the frame list */ + leaf0 = n / 2; + for(i = 0; i < 32; i++){ + o = 0; + for(d = 0; d < depth; d++){ + o <<= 1; + if(i & (1 << d)) + o |= 1; + } + if(leaf0 + o >= n){ + print("leaf0=%d o=%d i=%d n=%d\n", leaf0, o, i, n); + break; + } + ctlr->hcca->intrtable[i] = ptr2pa(tree[leaf0 + o]); + } + ctlr->tree = qt; +} + +static void +ohcimeminit(Ctlr *ctlr) +{ + Hcca *hcca; + + edfree(edalloc()); /* allocate pools now */ + tdfree(tdalloc()); + + hcca = xspanalloc(sizeof(Hcca), 256, 0); + if(hcca == nil) + panic("usbhreset: no memory for Hcca"); + hcca = KSEG1ADDR(hcca); // XXX + memset(hcca, 0, sizeof(*hcca)); + ctlr->hcca = hcca; + + mkqhtree(ctlr); +} + +static void +ohcireset(Ctlr *ctlr) +{ + ilock(ctlr); + dprint("ohci %#p reset\n", ctlr->ohci); + + /* + * usually enter here in reset, wait till its through, + * then do our own so we are on known timing conditions. + * Is this needed? + */ + delay(100); + ctlr->ohci->control = 0; + delay(100); + + /* legacy support register: turn off lunacy mode */ + pcicfgw16(ctlr->pcidev, 0xc0, 0x2000); + + iunlock(ctlr); +} + +static void +shutdown(Hci *hp) +{ + Ctlr *ctlr; + + ctlr = hp->aux; + + ilock(ctlr); + ctlr->ohci->intrdisable = Mie; + ctlr->ohci->control = 0; + coherence(); + delay(100); + iunlock(ctlr); +} + +static int +reset(Hci *hp) +{ + int i; + Ctlr *ctlr; + Pcidev *p; + static Lock resetlck; + + if(getconf("*nousbohci")) + return -1; + ilock(&resetlck); + scanpci(); + + /* + * 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->ohci){ + ctlr->active = 1; + break; + } + } + iunlock(&resetlck); + if(ctlrs[i] == nil || i == Nhcis) + return -1; + if(ctlr->ohci->control == ~0) + return -1; + + + p = ctlr->pcidev; + hp->aux = ctlr; + hp->port = (uintptr)ctlr->ohci; + hp->irq = ILpci; + hp->tbdf = p->tbdf; + ctlr->nports = hp->nports = ctlr->ohci->rhdesca & 0xff; + + ohcireset(ctlr); + ohcimeminit(ctlr); + + /* + * Linkage to the generic HCI driver. + */ + hp->init = init; + hp->dump = dump; + hp->interrupt = interrupt; + hp->epopen = epopen; + hp->epclose = epclose; + hp->epread = epread; + hp->epwrite = epwrite; + hp->seprintep = seprintep; + hp->portenable = portenable; + hp->portreset = portreset; + hp->portstatus = portstatus; + hp->shutdown = shutdown; + hp->debug = usbdebug; + hp->type = "ohci"; + return 0; +} + +void +usbohcilink(void) +{ + addhcitype("ohci", reset); +} --- /dev/null +++ /sys/src/9/loongson/words @@ -0,0 +1,59 @@ +lemote fuloong-2e mini-computer + +cpu: loongson 2e, mips le, 64 bit +(kernel is 32 bit) + +fpu +256mb ram (default) +mmu 64 tlbs +l1 i-cache 4 ways, 512 sets, 32 bytes per line = 64K +l1 d-cache 4 ways, 512 sets, 32 bytes per line = 64K +l2 cache 4 ways, 32 bytes per line, 512K, write back +config: 0x0003093b +no config1, 2, 3, etc. + +1 uart 8250 (actually 16550ish) +pci bus(es) +ether rtl 8139 00:07.0 +video ati radeon 515a 00:06.0 +ide disk via82cxxx 00:05.1 +usb 2 ohci, 2 uhci 00:05.2-3, 00:08.0-1 +audio via82c686 ac97 00:05.5 +isa bridge via82c686 00:05.0 + + +addresses + +devices; access by KSEG1|addr +0x10000000 pci memory space +0x14000000 vga fb 32M +0x16020000 vga 64K +0x16030000 usb ohci 4K +0x16031000 usb ohci 4K +0x16032000 usb ehci 256 +0x16032100 ether 256 +0x1fd00000 io ports + 03f8 uart + a000 ether 256 + a100 vga 256 + a300 audio 256 + a400 usb uhci 32 + a420 usb uhci 32 + a440 ide 16 + a450 audio 4+4 + +0x80100000 virtual start addr (linux) +0xbfc00000 prom + +irqs +2 pci +3 unused/unknown +4 uart +5 8259 +6 perf counter +7 clock + +pci sub-irqs +26 ether +26 usb ohci +27 usb ohci