diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
| commit | 74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch) | |
| tree | c6e220ba61db3a6ea4052e6841296d829654e664 /os/pc/devarch.c | |
| parent | 46439007cf417cbd9ac8049bb4122c890097a0fa (diff) | |
20060303
Diffstat (limited to 'os/pc/devarch.c')
| -rw-r--r-- | os/pc/devarch.c | 940 |
1 files changed, 940 insertions, 0 deletions
diff --git a/os/pc/devarch.c b/os/pc/devarch.c new file mode 100644 index 00000000..6a7b52fe --- /dev/null +++ b/os/pc/devarch.c @@ -0,0 +1,940 @@ +#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" + +typedef struct IOMap IOMap; +struct IOMap +{ + IOMap *next; + int reserved; + char tag[13]; + ulong start; + ulong end; +}; + +static struct +{ + Lock; + IOMap *m; + IOMap *free; + IOMap maps[32]; // some initial free maps + + QLock ql; // lock for reading map +} iomap; + +enum { + Qdir = 0, + Qioalloc = 1, + Qiob, + Qiow, + Qiol, + 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, + "ioalloc", { Qioalloc, 0 }, 0, 0444, + "iob", { Qiob, 0 }, 0, 0660, + "iow", { Qiow, 0 }, 0, 0660, + "iol", { Qiol, 0 }, 0, 0660, +}; +Lock archwlock; /* the lock is only for changing archdir */ +int narchdir = Qbase; +int (*_pcmspecial)(char*, ISAConf*); +void (*_pcmspecialclose)(int); + +static int doi8253set = 1; + +/* + * Add a file to the #P listing. Once added, you can't delete it. + * You can't add a file with the same name as one already there, + * and you get a pointer to the Dirtab entry so you can do things + * like change the Qid version. Changing the Qid path is disallowed. + */ +Dirtab* +addarchfile(char *name, int perm, Rdwrfn *rdfn, Rdwrfn *wrfn) +{ + int i; + Dirtab d; + Dirtab *dp; + + memset(&d, 0, sizeof d); + strcpy(d.name, name); + d.perm = perm; + + lock(&archwlock); + if(narchdir >= Qmax){ + unlock(&archwlock); + return nil; + } + + for(i=0; i<narchdir; i++) + if(strcmp(archdir[i].name, name) == 0){ + unlock(&archwlock); + return nil; + } + + d.qid.path = narchdir; + archdir[narchdir] = d; + readfn[narchdir] = rdfn; + writefn[narchdir] = wrfn; + dp = &archdir[narchdir++]; + unlock(&archwlock); + + return dp; +} + +void +ioinit(void) +{ + char *excluded; + int i; + + for(i = 0; i < nelem(iomap.maps)-1; i++) + iomap.maps[i].next = &iomap.maps[i+1]; + iomap.maps[i].next = nil; + iomap.free = iomap.maps; + + /* + * This is necessary to make the IBM X20 boot. + * Have not tracked down the reason. + */ + ioalloc(0x0fff, 1, 0, "dummy"); // i82557 is at 0x1000, the dummy + // entry is needed for swappable devs. + + if ((excluded = getconf("ioexclude")) != nil) { + char *s; + + s = excluded; + while (s && *s != '\0' && *s != '\n') { + char *ends; + int io_s, io_e; + + io_s = (int)strtol(s, &ends, 0); + if (ends == nil || ends == s || *ends != '-') { + print("ioinit: cannot parse option string\n"); + break; + } + s = ++ends; + + io_e = (int)strtol(s, &ends, 0); + if (ends && *ends == ',') + *ends++ = '\0'; + s = ends; + + ioalloc(io_s, io_e - io_s + 1, 0, "pre-allocated"); + } + } + +} + +// Reserve a range to be ioalloced later. +// This is in particular useful for exchangable cards, such +// as pcmcia and cardbus cards. +int +ioreserve(int, int size, int align, char *tag) +{ + IOMap *m, **l; + int i, port; + + lock(&iomap); + // find a free port above 0x400 and below 0x1000 + port = 0x400; + for(l = &iomap.m; *l; l = &(*l)->next){ + m = *l; + if (m->start < 0x400) continue; + i = m->start - port; + if(i > size) + break; + if(align > 0) + port = ((port+align-1)/align)*align; + else + port = m->end; + } + if(*l == nil){ + unlock(&iomap); + return -1; + } + m = iomap.free; + if(m == nil){ + print("ioalloc: out of maps"); + unlock(&iomap); + return port; + } + iomap.free = m->next; + m->next = *l; + m->start = port; + m->end = port + size; + m->reserved = 1; + strncpy(m->tag, tag, sizeof(m->tag)); + m->tag[sizeof(m->tag)-1] = 0; + *l = m; + + archdir[0].qid.vers++; + + unlock(&iomap); + return m->start; +} + +// +// alloc some io port space and remember who it was +// alloced to. if port < 0, find a free region. +// +int +ioalloc(int port, int size, int align, char *tag) +{ + IOMap *m, **l; + int i; + + lock(&iomap); + if(port < 0){ + // find a free port above 0x400 and below 0x1000 + port = 0x400; + for(l = &iomap.m; *l; l = &(*l)->next){ + m = *l; + if (m->start < 0x400) continue; + i = m->start - port; + if(i > size) + break; + if(align > 0) + port = ((port+align-1)/align)*align; + else + port = m->end; + } + if(*l == nil){ + unlock(&iomap); + return -1; + } + } else { + // Only 64KB I/O space on the x86. + if((port+size) > 0x10000){ + unlock(&iomap); + return -1; + } + // see if the space clashes with previously allocated ports + for(l = &iomap.m; *l; l = &(*l)->next){ + m = *l; + if(m->end <= port) + continue; + if(m->reserved && m->start == port && m->end == port + size) { + m->reserved = 0; + unlock(&iomap); + return m->start; + } + if(m->start >= port+size) + break; + unlock(&iomap); + return -1; + } + } + m = iomap.free; + if(m == nil){ + print("ioalloc: out of maps"); + unlock(&iomap); + return port; + } + iomap.free = m->next; + m->next = *l; + m->start = port; + m->end = port + size; + strncpy(m->tag, tag, sizeof(m->tag)); + m->tag[sizeof(m->tag)-1] = 0; + *l = m; + + archdir[0].qid.vers++; + + unlock(&iomap); + return m->start; +} + +void +iofree(int port) +{ + IOMap *m, **l; + + lock(&iomap); + for(l = &iomap.m; *l; l = &(*l)->next){ + if((*l)->start == port){ + m = *l; + *l = m->next; + m->next = iomap.free; + iomap.free = m; + break; + } + if((*l)->start > port) + break; + } + archdir[0].qid.vers++; + unlock(&iomap); +} + +int +iounused(int start, int end) +{ + IOMap *m; + + for(m = iomap.m; m; m = m->next){ + if(start >= m->start && start < m->end + || start <= m->start && end > m->start) + return 0; + } + return 1; +} + +static void +checkport(int start, int end) +{ + /* standard vga regs are OK */ + if(start >= 0x2b0 && end <= 0x2df+1) + return; + if(start >= 0x3c0 && end <= 0x3da+1) + return; + + if(iounused(start, end)) + return; + error(Eperm); +} + +static Chan* +archattach(char* spec) +{ + return devattach('P', spec); +} + +Walkqid* +archwalk(Chan* c, Chan *nc, char** name, int nname) +{ + return devwalk(c, nc, name, nname, archdir, narchdir, devgen); +} + +static int +archstat(Chan* c, uchar* dp, int n) +{ + return devstat(c, dp, n, archdir, narchdir, devgen); +} + +static Chan* +archopen(Chan* c, int omode) +{ + return devopen(c, omode, archdir, narchdir, devgen); +} + +static void +archclose(Chan*) +{ +} + +enum +{ + Linelen= 31, +}; + +static long +archread(Chan *c, void *a, long n, vlong offset) +{ + char *buf, *p; + int port; + ushort *sp; + ulong *lp; + IOMap *m; + Rdwrfn *fn; + + switch((ulong)c->qid.path){ + + case Qdir: + return devdirread(c, a, n, archdir, narchdir, devgen); + + case Qiob: + port = offset; + checkport(offset, offset+n); + for(p = a; port < offset+n; port++) + *p++ = inb(port); + return n; + + case Qiow: + if(n & 1) + error(Ebadarg); + checkport(offset, offset+n); + sp = a; + for(port = offset; port < offset+n; port += 2) + *sp++ = ins(port); + return n; + + case Qiol: + if(n & 3) + error(Ebadarg); + checkport(offset, offset+n); + lp = a; + for(port = offset; port < offset+n; port += 4) + *lp++ = inl(port); + return n; + + case Qioalloc: + break; + + default: + if(c->qid.path < narchdir && (fn = readfn[c->qid.path])) + return fn(c, a, n, offset); + error(Eperm); + break; + } + + if((buf = malloc(n)) == nil) + error(Enomem); + p = buf; + n = n/Linelen; + offset = offset/Linelen; + + lock(&iomap); + for(m = iomap.m; n > 0 && m != nil; m = m->next){ + if(offset-- > 0) + continue; + sprint(p, "%8lux %8lux %-12.12s\n", m->start, m->end-1, m->tag); + p += Linelen; + n--; + } + unlock(&iomap); + + n = p - buf; + memmove(a, buf, n); + free(buf); + + return n; +} + +static long +archwrite(Chan *c, void *a, long n, vlong offset) +{ + char *p; + int port; + ushort *sp; + ulong *lp; + Rdwrfn *fn; + + switch((ulong)c->qid.path){ + + case Qiob: + p = a; + checkport(offset, offset+n); + for(port = offset; port < offset+n; port++) + outb(port, *p++); + return n; + + case Qiow: + if(n & 1) + error(Ebadarg); + checkport(offset, offset+n); + sp = a; + for(port = offset; port < offset+n; port += 2) + outs(port, *sp++); + return n; + + case Qiol: + if(n & 3) + error(Ebadarg); + checkport(offset, offset+n); + lp = a; + for(port = offset; port < offset+n; port += 4) + outl(port, *lp++); + return n; + + default: + if(c->qid.path < narchdir && (fn = writefn[c->qid.path])) + return fn(c, a, n, offset); + error(Eperm); + break; + } + return 0; +} + +Dev archdevtab = { + 'P', + "arch", + + devreset, + devinit, + devshutdown, + archattach, + archwalk, + archstat, + archopen, + devcreate, + archclose, + archread, + devbread, + archwrite, + devbwrite, + devremove, + devwstat, +}; + +/* + * the following is a generic version of the + * architecture specific stuff + */ + +static int +unimplemented(int) +{ + return 0; +} + +static void +nop(void) +{ +} + +/* + * On a uniprocessor, you'd think that coherence could be nop, + * but it can't. We still need a barrier when using coherence() in + * device drivers. + * + * On VMware, it's safe (and a huge win) to set this to nop. + * Aux/vmware does this via the #P/archctl file. + */ +void (*coherence)(void) = nop; + +PCArch* arch; +extern PCArch* knownarch[]; + +PCArch archgeneric = { +.id= "generic", +.ident= 0, +.reset= i8042reset, +.serialpower= unimplemented, +.modempower= unimplemented, + +.intrinit= i8259init, +.intrenable= i8259enable, +.intrvecno= i8259vecno, +.intrdisable= i8259disable, + +.clockenable= i8253enable, +.fastclock= i8253read, +.timerset= i8253timerset, +}; + +typedef struct X86type X86type; +struct X86type { + int family; + int model; + int aalcycles; + char* name; +}; + +static X86type x86intel[] = +{ + { 4, 0, 22, "486DX", }, /* known chips */ + { 4, 1, 22, "486DX50", }, + { 4, 2, 22, "486SX", }, + { 4, 3, 22, "486DX2", }, + { 4, 4, 22, "486SL", }, + { 4, 5, 22, "486SX2", }, + { 4, 7, 22, "DX2WB", }, /* P24D */ + { 4, 8, 22, "DX4", }, /* P24C */ + { 4, 9, 22, "DX4WB", }, /* P24CT */ + { 5, 0, 23, "P5", }, + { 5, 1, 23, "P5", }, + { 5, 2, 23, "P54C", }, + { 5, 3, 23, "P24T", }, + { 5, 4, 23, "P55C MMX", }, + { 5, 7, 23, "P54C VRT", }, + { 6, 1, 16, "PentiumPro", },/* trial and error */ + { 6, 3, 16, "PentiumII", }, + { 6, 5, 16, "PentiumII/Xeon", }, + { 6, 6, 16, "Celeron", }, + { 6, 7, 16, "PentiumIII/Xeon", }, + { 6, 8, 16, "PentiumIII/Xeon", }, + { 6, 0xB, 16, "PentiumIII/Xeon", }, + { 0xF, 1, 16, "P4", }, /* P4 */ + { 0xF, 2, 16, "PentiumIV/Xeon", }, + + { 3, -1, 32, "386", }, /* family defaults */ + { 4, -1, 22, "486", }, + { 5, -1, 23, "P5", }, + { 6, -1, 16, "P6", }, + { 0xF, -1, 16, "P4", }, /* P4 */ + + { -1, -1, 16, "unknown", }, /* total default */ +}; + +/* + * The AMD processors all implement the CPUID instruction. + * The later ones also return the processor name via functions + * 0x80000002, 0x80000003 and 0x80000004 in registers AX, BX, CX + * and DX: + * K5 "AMD-K5(tm) Processor" + * K6 "AMD-K6tm w/ multimedia extensions" + * K6 3D "AMD-K6(tm) 3D processor" + * K6 3D+ ? + */ +static X86type x86amd[] = +{ + { 5, 0, 23, "AMD-K5", }, /* guesswork */ + { 5, 1, 23, "AMD-K5", }, /* guesswork */ + { 5, 2, 23, "AMD-K5", }, /* guesswork */ + { 5, 3, 23, "AMD-K5", }, /* guesswork */ + { 5, 6, 11, "AMD-K6", }, /* trial and error */ + { 5, 7, 11, "AMD-K6", }, /* trial and error */ + { 5, 8, 11, "AMD-K6-2", }, /* trial and error */ + { 5, 9, 11, "AMD-K6-III", },/* trial and error */ + + { 6, 1, 11, "AMD-Athlon", },/* trial and error */ + { 6, 2, 11, "AMD-Athlon", },/* trial and error */ + + { 4, -1, 22, "Am486", }, /* guesswork */ + { 5, -1, 23, "AMD-K5/K6", }, /* guesswork */ + { 6, -1, 11, "AMD-Athlon", },/* guesswork */ + { 0xF, -1, 11, "AMD64", }, /* guesswork */ + + { -1, -1, 11, "unknown", }, /* total default */ +}; + +/* + * WinChip 240MHz + */ +static X86type x86winchip[] = +{ + {5, 4, 23, "Winchip",}, /* guesswork */ + {6, 7, 23, "Via C3 Samuel 2 or Ezra",}, + {6, 8, 23, "Via C3 Ezra-T",}, + { -1, -1, 23, "unknown", }, /* total default */ +}; + +/* + * SiS 55x + */ +static X86type x86sis[] = +{ + {5, 0, 23, "SiS 55x",}, /* guesswork */ + { -1, -1, 23, "unknown", }, /* total default */ +}; + +static X86type *cputype; + +static void simplecycles(uvlong*); +void (*cycles)(uvlong*) = simplecycles; +void _cycles(uvlong*); /* in l.s */ + +static void +simplecycles(uvlong*x) +{ + *x = m->ticks; +} + +void +cpuidprint(void) +{ + int i; + char buf[128]; + + i = sprint(buf, "cpu%d: %dMHz ", m->machno, m->cpumhz); + if(m->cpuidid[0]) + i += sprint(buf+i, "%12.12s ", m->cpuidid); + sprint(buf+i, "%s (cpuid: AX 0x%4.4uX DX 0x%4.4uX)\n", + m->cpuidtype, m->cpuidax, m->cpuiddx); + print(buf); +} + +/* + * figure out: + * - cpu type + * - whether or not we have a TSC (cycle counter) + * - whether or not it supports page size extensions + * (if so turn it on) + * - whether or not it supports machine check exceptions + * (if so turn it on) + * - whether or not it supports the page global flag + * (if so turn it on) + */ +int +cpuidentify(void) +{ + char *p; + int family, model, nomce; + X86type *t, *tab; + ulong cr4; + vlong mca, mct; + + cpuid(m->cpuidid, &m->cpuidax, &m->cpuiddx); + if(strncmp(m->cpuidid, "AuthenticAMD", 12) == 0) + tab = x86amd; + else if(strncmp(m->cpuidid, "CentaurHauls", 12) == 0) + tab = x86winchip; + else if(strncmp(m->cpuidid, "SiS SiS SiS ", 12) == 0) + tab = x86sis; + else + tab = x86intel; + + family = X86FAMILY(m->cpuidax); + model = X86MODEL(m->cpuidax); + for(t=tab; t->name; t++) + if((t->family == family && t->model == model) + || (t->family == family && t->model == -1) + || (t->family == -1)) + break; + + m->cpuidtype = t->name; + + /* + * if there is one, set tsc to a known value + */ + if(m->cpuiddx & 0x10){ + m->havetsc = 1; + cycles = _cycles; + if(m->cpuiddx & 0x20) + wrmsr(0x10, 0); + } + + /* + * use i8253 to guess our cpu speed + */ + guesscpuhz(t->aalcycles); + + /* + * If machine check exception, page size extensions or page global bit + * are supported enable them in CR4 and clear any other set extensions. + * If machine check was enabled clear out any lingering status. + */ + if(m->cpuiddx & 0x2088){ + cr4 = 0; + if(m->cpuiddx & 0x08) + cr4 |= 0x10; /* page size extensions */ + if(p = getconf("*nomce")) + nomce = strtoul(p, 0, 0); + else + nomce = 0; + if((m->cpuiddx & 0x80) && !nomce){ + cr4 |= 0x40; /* machine check enable */ + if(family == 5){ + rdmsr(0x00, &mca); + rdmsr(0x01, &mct); + } + } + + /* + * Detect whether the chip supports the global bit + * in page directory and page table entries. When set + * in a particular entry, it means ``don't bother removing + * this from the TLB when CR3 changes.'' + * + * We flag all kernel pages with this bit. Doing so lessens the + * overhead of switching processes on bare hardware, + * even more so on VMware. See mmu.c:/^memglobal. + * + * For future reference, should we ever need to do a + * full TLB flush, it can be accomplished by clearing + * the PGE bit in CR4, writing to CR3, and then + * restoring the PGE bit. + */ + if(m->cpuiddx & 0x2000){ + cr4 |= 0x80; /* page global enable bit */ + m->havepge = 1; + } + + putcr4(cr4); + if(m->cpuiddx & 0x80) + rdmsr(0x01, &mct); + } + + cputype = t; + return t->family; +} + +static long +cputyperead(Chan*, void *a, long n, vlong offset) +{ + char str[32]; + ulong mhz; + + mhz = (m->cpuhz+999999)/1000000; + + snprint(str, sizeof(str), "%s %lud\n", cputype->name, mhz); + return readstr(offset, a, n, str); +} + +static long +archctlread(Chan*, void *a, long nn, vlong offset) +{ + char buf[256]; + int n; + + n = snprint(buf, sizeof buf, "cpu %s %lud%s\n", + cputype->name, (ulong)(m->cpuhz+999999)/1000000, + m->havepge ? " pge" : ""); + n += snprint(buf+n, sizeof buf-n, "pge %s\n", getcr4()&0x80 ? "on" : "off"); + n += snprint(buf+n, sizeof buf-n, "coherence "); + if(coherence == mb386) + n += snprint(buf+n, sizeof buf-n, "mb386\n"); + else if(coherence == mb586) + n += snprint(buf+n, sizeof buf-n, "mb586\n"); + else if(coherence == nop) + n += snprint(buf+n, sizeof buf-n, "nop\n"); + else + n += snprint(buf+n, sizeof buf-n, "0x%p\n", coherence); + n += snprint(buf+n, sizeof buf-n, "i8253set %s\n", doi8253set ? "on" : "off"); + buf[n] = 0; + return readstr(offset, a, nn, buf); +} + +enum +{ + CMpge, + CMcoherence, + CMi8253set, +}; + +static Cmdtab archctlmsg[] = +{ + CMpge, "pge", 2, + CMcoherence, "coherence", 2, + CMi8253set, "i8253set", 2, +}; + +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)); + switch(ct->index){ + case CMpge: + if(!m->havepge) + error("processor does not support pge"); + if(strcmp(cb->f[1], "on") == 0) + putcr4(getcr4() | 0x80); + else if(strcmp(cb->f[1], "off") == 0) + putcr4(getcr4() & ~0x80); + else + cmderror(cb, "invalid pge ctl"); + break; + case CMcoherence: + if(strcmp(cb->f[1], "mb386") == 0) + coherence = mb386; + else if(strcmp(cb->f[1], "mb586") == 0){ + if(X86FAMILY(m->cpuidax) < 5) + error("invalid coherence ctl on this cpu family"); + coherence = mb586; + } + else if(strcmp(cb->f[1], "nop") == 0){ + /* only safe on vmware */ + if(conf.nmach > 1) + error("cannot disable coherence on a multiprocessor"); + coherence = nop; + }else + cmderror(cb, "invalid coherence ctl"); + break; + case CMi8253set: + if(strcmp(cb->f[1], "on") == 0) + doi8253set = 1; + else if(strcmp(cb->f[1], "off") == 0){ + doi8253set = 0; + (*arch->timerset)(0); + }else + cmderror(cb, "invalid i2853set ctl"); + break; + } + free(cb); + poperror(); + return n; +} + +void +archinit(void) +{ + PCArch **p; + + arch = 0; + for(p = knownarch; *p; p++){ + if((*p)->ident && (*p)->ident() == 0){ + arch = *p; + break; + } + } + if(arch == 0) + arch = &archgeneric; + else{ + if(arch->id == 0) + arch->id = archgeneric.id; + if(arch->reset == 0) + arch->reset = archgeneric.reset; + if(arch->serialpower == 0) + arch->serialpower = archgeneric.serialpower; + if(arch->modempower == 0) + arch->modempower = archgeneric.modempower; + if(arch->intrinit == 0) + arch->intrinit = archgeneric.intrinit; + if(arch->intrenable == 0) + arch->intrenable = archgeneric.intrenable; + } + + /* + * Decide whether to use copy-on-reference (386 and mp). + * We get another chance to set it in mpinit() for a + * multiprocessor. + */ + if(X86FAMILY(m->cpuidax) == 3) + conf.copymode = 1; + + if(X86FAMILY(m->cpuidax) >= 5) + coherence = mb586; + + addarchfile("cputype", 0444, cputyperead, nil); + addarchfile("archctl", 0664, archctlread, archctlwrite); +} + +/* + * call either the pcmcia or pccard device setup + */ +int +pcmspecial(char *idstr, ISAConf *isa) +{ + return (_pcmspecial != nil)? _pcmspecial(idstr, isa): -1; +} + +/* + * call either the pcmcia or pccard device teardown + */ +void +pcmspecialclose(int a) +{ + if (_pcmspecialclose != nil) + _pcmspecialclose(a); +} + +/* + * return value and speed of timer set in arch->clockenable + */ +uvlong +fastticks(uvlong *hz) +{ + return (*arch->fastclock)(hz); +} + +/* + * set next timer interrupt + */ +void +timerset(uvlong x) +{ + if(doi8253set) + (*arch->timerset)(x); +} |
