|  | /* | 
|  | * This file is part of the UCB release of Plan 9. It is subject to the license | 
|  | * terms in the LICENSE file found in the top-level directory of this | 
|  | * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No | 
|  | * part of the UCB release of Plan 9, including this file, may be copied, | 
|  | * modified, propagated, or distributed except according to the terms contained | 
|  | * in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include <vfs.h> | 
|  | #include <kfs.h> | 
|  | #include <slab.h> | 
|  | #include <kmalloc.h> | 
|  | #include <kref.h> | 
|  | #include <string.h> | 
|  | #include <stdio.h> | 
|  | #include <assert.h> | 
|  | #include <error.h> | 
|  | #include <cpio.h> | 
|  | #include <pmap.h> | 
|  | #include <smp.h> | 
|  | #include <ip.h> | 
|  | #include <time.h> | 
|  |  | 
|  | typedef struct IOMap IOMap; | 
|  | struct IOMap { | 
|  | IOMap *next; | 
|  | int reserved; | 
|  | char tag[13]; | 
|  | uint32_t start; | 
|  | uint32_t end; | 
|  | }; | 
|  |  | 
|  | static struct { | 
|  | spinlock_t lock; | 
|  | IOMap *map; | 
|  | IOMap *free; | 
|  | IOMap maps[32];				// some initial free maps | 
|  |  | 
|  | qlock_t ql;					// lock for reading map | 
|  | } iomap; | 
|  |  | 
|  | enum { | 
|  | Qdir = 0, | 
|  | Qioalloc = 1, | 
|  | Qiob, | 
|  | Qiow, | 
|  | Qiol, | 
|  | Qgdb, | 
|  | Qbase, | 
|  | Qmapram, | 
|  | Qrealmem, | 
|  |  | 
|  | Qmax = 16, | 
|  | }; | 
|  |  | 
|  | typedef long Rdwrfn(struct chan *, void *, long, int64_t); | 
|  |  | 
|  | static Rdwrfn *readfn[Qmax]; | 
|  | static Rdwrfn *writefn[Qmax]; | 
|  |  | 
|  | static struct dirtab archdir[Qmax] = { | 
|  | {".", {Qdir, 0, QTDIR}, 0, 0555}, | 
|  | {"ioalloc", {Qioalloc, 0}, 0, 0444}, | 
|  | {"iob", {Qiob, 0}, 0, 0666}, | 
|  | {"iow", {Qiow, 0}, 0, 0666}, | 
|  | {"iol", {Qiol, 0}, 0, 0666}, | 
|  | {"gdb", {Qgdb, 0}, 0, 0660}, | 
|  | {"mapram", {Qmapram, 0}, 0, 0444}, | 
|  | {"realmodemem", {Qrealmem, 0}, 0, 0660}, | 
|  | }; | 
|  |  | 
|  | spinlock_t archwlock;			/* the lock is only for changing archdir */ | 
|  | int narchdir = Qbase; | 
|  | int gdbactive = 0; | 
|  |  | 
|  | /* If we use these, put this in a header */ | 
|  | int ioalloc(int port, int size, int align, char *tag); | 
|  |  | 
|  | /* | 
|  | * 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. | 
|  | */ | 
|  | struct dirtab *addarchfile(char *name, int perm, Rdwrfn * rdfn, Rdwrfn * wrfn) | 
|  | { | 
|  | int i; | 
|  | struct dirtab d; | 
|  | struct dirtab *dp; | 
|  |  | 
|  | memset(&d, 0, sizeof d); | 
|  | strncpy(d.name, name, sizeof(d.name)); | 
|  | d.perm = perm; | 
|  |  | 
|  | spin_lock(&archwlock); | 
|  | if (narchdir >= Qmax) { | 
|  | spin_unlock(&archwlock); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < narchdir; i++) | 
|  | if (strcmp(archdir[i].name, name) == 0) { | 
|  | spin_unlock(&archwlock); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | d.qid.path = narchdir; | 
|  | archdir[narchdir] = d; | 
|  | readfn[narchdir] = rdfn; | 
|  | writefn[narchdir] = wrfn; | 
|  | dp = &archdir[narchdir++]; | 
|  | spin_unlock(&archwlock); | 
|  |  | 
|  | return dp; | 
|  | } | 
|  |  | 
|  | void ioinit(void) | 
|  | { | 
|  | int i; | 
|  | char *excluded = ""; | 
|  |  | 
|  | panic("Akaros doesn't do IO port allocation yet.  Don't init."); | 
|  | for (i = 0; i < ARRAY_SIZE(iomap.maps) - 1; i++) | 
|  | iomap.maps[i].next = &iomap.maps[i + 1]; | 
|  | iomap.maps[i].next = NULL; | 
|  | iomap.free = iomap.maps; | 
|  | 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 == NULL || ends == s || *ends != '-') { | 
|  | printd("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 unused_int, int size, int align, char *tag) | 
|  | { | 
|  | IOMap *map, **l; | 
|  | int i, port; | 
|  |  | 
|  | spin_lock(&(&iomap)->lock); | 
|  | // find a free port above 0x400 and below 0x1000 | 
|  | port = 0x400; | 
|  | for (l = &iomap.map; *l; l = &(*l)->next) { | 
|  | map = *l; | 
|  | if (map->start < 0x400) | 
|  | continue; | 
|  | i = map->start - port; | 
|  | if (i > size) | 
|  | break; | 
|  | if (align > 0) | 
|  | port = ((port + align - 1) / align) * align; | 
|  | else | 
|  | port = map->end; | 
|  | } | 
|  | if (*l == NULL) { | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return -1; | 
|  | } | 
|  | map = iomap.free; | 
|  | if (map == NULL) { | 
|  | printd("ioalloc: out of maps"); | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return port; | 
|  | } | 
|  | iomap.free = map->next; | 
|  | map->next = *l; | 
|  | map->start = port; | 
|  | map->end = port + size; | 
|  | map->reserved = 1; | 
|  | strncpy(map->tag, tag, sizeof(map->tag)); | 
|  | map->tag[sizeof(map->tag) - 1] = 0; | 
|  | *l = map; | 
|  |  | 
|  | archdir[0].qid.vers++; | 
|  |  | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return map->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 *map, **l; | 
|  | int i; | 
|  |  | 
|  | spin_lock(&(&iomap)->lock); | 
|  | if (port < 0) { | 
|  | // find a free port above 0x400 and below 0x1000 | 
|  | port = 0x400; | 
|  | for (l = &iomap.map; *l; l = &(*l)->next) { | 
|  | map = *l; | 
|  | if (map->start < 0x400) | 
|  | continue; | 
|  | i = map->start - port; | 
|  | if (i > size) | 
|  | break; | 
|  | if (align > 0) | 
|  | port = ((port + align - 1) / align) * align; | 
|  | else | 
|  | port = map->end; | 
|  | } | 
|  | if (*l == NULL) { | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return -1; | 
|  | } | 
|  | } else { | 
|  | // Only 64KB I/O space on the x86. | 
|  | if ((port + size) > 0x10000) { | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return -1; | 
|  | } | 
|  | // see if the space clashes with previously allocated ports | 
|  | for (l = &iomap.map; *l; l = &(*l)->next) { | 
|  | map = *l; | 
|  | if (map->end <= port) | 
|  | continue; | 
|  | if (map->reserved && map->start == port && map->end == port + size) { | 
|  | map->reserved = 0; | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return map->start; | 
|  | } | 
|  | if (map->start >= port + size) | 
|  | break; | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return -1; | 
|  | } | 
|  | } | 
|  | map = iomap.free; | 
|  | if (map == NULL) { | 
|  | printd("ioalloc: out of maps"); | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return port; | 
|  | } | 
|  | iomap.free = map->next; | 
|  | map->next = *l; | 
|  | map->start = port; | 
|  | map->end = port + size; | 
|  | strncpy(map->tag, tag, sizeof(map->tag)); | 
|  | map->tag[sizeof(map->tag) - 1] = 0; | 
|  | *l = map; | 
|  |  | 
|  | archdir[0].qid.vers++; | 
|  |  | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | return map->start; | 
|  | } | 
|  |  | 
|  | void iofree(int port) | 
|  | { | 
|  | IOMap *map, **l; | 
|  |  | 
|  | spin_lock(&(&iomap)->lock); | 
|  | for (l = &iomap.map; *l; l = &(*l)->next) { | 
|  | if ((*l)->start == port) { | 
|  | map = *l; | 
|  | *l = map->next; | 
|  | map->next = iomap.free; | 
|  | iomap.free = map; | 
|  | break; | 
|  | } | 
|  | if ((*l)->start > port) | 
|  | break; | 
|  | } | 
|  | archdir[0].qid.vers++; | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | } | 
|  |  | 
|  | int iounused(int start, int end) | 
|  | { | 
|  | IOMap *map; | 
|  |  | 
|  | for (map = iomap.map; map; map = map->next) { | 
|  | if (((start >= map->start) && (start < map->end)) | 
|  | || ((start <= map->start) && (end > map->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 struct chan *archattach(char *spec) | 
|  | { | 
|  | return devattach('P', spec); | 
|  | } | 
|  |  | 
|  | struct walkqid *archwalk(struct chan *c, struct chan *nc, char **name, | 
|  | int nname) | 
|  | { | 
|  | return devwalk(c, nc, name, nname, archdir, narchdir, devgen); | 
|  | } | 
|  |  | 
|  | static int archstat(struct chan *c, uint8_t * dp, int n) | 
|  | { | 
|  | return devstat(c, dp, n, archdir, narchdir, devgen); | 
|  | } | 
|  |  | 
|  | static struct chan *archopen(struct chan *c, int omode) | 
|  | { | 
|  | return devopen(c, omode, archdir, narchdir, devgen); | 
|  | } | 
|  |  | 
|  | static void archclose(struct chan *unused) | 
|  | { | 
|  | } | 
|  |  | 
|  | enum { | 
|  | Linelen = 31, | 
|  | }; | 
|  |  | 
|  | static long archread(struct chan *c, void *a, long n, int64_t offset) | 
|  | { | 
|  | char *buf, *p; | 
|  | int port; | 
|  | uint16_t *sp; | 
|  | uint32_t *lp; | 
|  | IOMap *map; | 
|  | Rdwrfn *fn; | 
|  |  | 
|  | switch ((uint32_t) c->qid.path) { | 
|  |  | 
|  | case Qdir: | 
|  | return devdirread(c, a, n, archdir, narchdir, devgen); | 
|  |  | 
|  | case Qgdb: | 
|  | p = gdbactive ? "1" : "0"; | 
|  | return readstr(offset, a, n, p); | 
|  | 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++ = inw(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 = kzmalloc(n, 0)) == NULL) | 
|  | error(Enomem); | 
|  | p = buf; | 
|  | n = n / Linelen; | 
|  | offset = offset / Linelen; | 
|  |  | 
|  | switch ((uint32_t) c->qid.path) { | 
|  | case Qioalloc: | 
|  | spin_lock(&(&iomap)->lock); | 
|  | for (map = iomap.map; n > 0 && map != NULL; map = map->next) { | 
|  | if (offset-- > 0) | 
|  | continue; | 
|  | snprintf(p, n * Linelen, "%#8p %#8p %-12.12s\n", map->start, | 
|  | map->end - 1, map->tag); | 
|  | p += Linelen; | 
|  | n--; | 
|  | } | 
|  | spin_unlock(&(&iomap)->lock); | 
|  | break; | 
|  | case Qmapram: | 
|  | error("Not yet"); | 
|  | break; | 
|  | } | 
|  |  | 
|  | n = p - buf; | 
|  | memmove(a, buf, n); | 
|  | kfree(buf); | 
|  |  | 
|  | return n; | 
|  | } | 
|  |  | 
|  | static long archwrite(struct chan *c, void *a, long n, int64_t offset) | 
|  | { | 
|  | char *p; | 
|  | int port; | 
|  | uint16_t *sp; | 
|  | uint32_t *lp; | 
|  | Rdwrfn *fn; | 
|  |  | 
|  | switch ((uint32_t) c->qid.path) { | 
|  |  | 
|  | case Qgdb: | 
|  | p = a; | 
|  | if (n != 1) | 
|  | error("Gdb: Write one byte, '1' or '0'"); | 
|  | if (*p == '1') | 
|  | gdbactive = 1; | 
|  | else if (*p == '0') | 
|  | gdbactive = 0; | 
|  | else | 
|  | error("Gdb: must be 1 or 0"); | 
|  | return 1; | 
|  |  | 
|  | 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) | 
|  | outw(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; | 
|  | } | 
|  |  | 
|  | struct dev archdevtab __devtab = { | 
|  | 'P', | 
|  | "arch", | 
|  |  | 
|  | devreset, | 
|  | devinit, | 
|  | devshutdown, | 
|  | archattach, | 
|  | archwalk, | 
|  | archstat, | 
|  | archopen, | 
|  | devcreate, | 
|  | archclose, | 
|  | archread, | 
|  | devbread, | 
|  | archwrite, | 
|  | devbwrite, | 
|  | devremove, | 
|  | devwstat, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | */ | 
|  | void nop(void) | 
|  | { | 
|  | } | 
|  |  | 
|  | static long cputyperead(struct chan *unused, void *a, long n, int64_t off) | 
|  | { | 
|  | char buf[512], *s, *e; | 
|  | int i, k; | 
|  | error("unimplemented"); | 
|  | #if 0 | 
|  | e = buf + sizeof buf; | 
|  | s = seprintf(buf, e, "%s %d\n", "AMD64", 0); | 
|  | k = m->ncpuinfoe - m->ncpuinfos; | 
|  | if (k > 4) | 
|  | k = 4; | 
|  | for (i = 0; i < k; i++) | 
|  | s = seprintf(s, e, "%#8.8ux %#8.8ux %#8.8ux %#8.8ux\n", | 
|  | m->cpuinfo[i][0], m->cpuinfo[i][1], | 
|  | m->cpuinfo[i][2], m->cpuinfo[i][3]); | 
|  | return readstr(off, a, n, buf); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static long rmemrw(int isr, void *a, long n, int64_t off) | 
|  | { | 
|  | if (off < 0) | 
|  | error("offset must be >= 0"); | 
|  | if (n < 0) | 
|  | error("count must be >= 0"); | 
|  | if (isr) { | 
|  | if (off >= MB) | 
|  | error("offset must be < 1MB"); | 
|  | if (off + n >= MB) | 
|  | n = MB - off; | 
|  | memmove(a, KADDR((uint32_t) off), n); | 
|  | } else { | 
|  | /* realmode buf page ok, allow vga framebuf's access */ | 
|  | if (off >= MB) | 
|  | error("offset must be < 1MB"); | 
|  | if (off + n > MB && (off < 0xA0000 || off + n > 0xB0000 + 0x10000)) | 
|  | error("bad offset/count in write"); | 
|  | memmove(KADDR((uint32_t) off), a, n); | 
|  | } | 
|  | return n; | 
|  | } | 
|  |  | 
|  | static long rmemread(struct chan *unused, void *a, long n, int64_t off) | 
|  | { | 
|  | return rmemrw(1, a, n, off); | 
|  | } | 
|  |  | 
|  | static long rmemwrite(struct chan *unused, void *a, long n, int64_t off) | 
|  | { | 
|  | return rmemrw(0, a, n, off); | 
|  | } | 
|  |  | 
|  | void archinit(void) | 
|  | { | 
|  | spinlock_init(&archwlock); | 
|  | addarchfile("cputype", 0444, cputyperead, NULL); | 
|  | addarchfile("realmodemem", 0660, rmemread, rmemwrite); | 
|  | } | 
|  |  | 
|  | void archreset(void) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | /* | 
|  | * And sometimes there is no keyboard... | 
|  | * | 
|  | * The reset register (0xcf9) is usually in one of the bridge | 
|  | * chips. The actual location and sequence could be extracted from | 
|  | * ACPI but why bother, this is the end of the line anyway. | 
|  | print("Takes a licking and keeps on ticking...\n"); | 
|  | */ | 
|  | i = inb(0xcf9);	/* ICHx reset control */ | 
|  | i &= 0x06; | 
|  | outb(0xcf9, i | 0x02);	/* SYS_RST */ | 
|  | udelay(1000); | 
|  | outb(0xcf9, i | 0x06);	/* RST_CPU transition */ | 
|  |  | 
|  | udelay(100 * 1000); | 
|  |  | 
|  | /* some broken hardware -- as well as qemu -- might | 
|  | * never reboot anyway with cf9. This is a standard | 
|  | * keyboard reboot sequence known to work on really | 
|  | * broken stuff -- like qemu. If there is no | 
|  | * keyboard it will do no harm. | 
|  | */ | 
|  | for (;;) { | 
|  | (void)inb(0x64); | 
|  | outb(0x64, 0xFE); | 
|  | udelay(100 * 1000); | 
|  | } | 
|  | } |