| /* |
| * 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); |
| } |
| } |