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