blob: a97d59d173fc3e46ec9814ad052c8741b46140d7 [file] [log] [blame]
/*
* 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);
}
}