#include	"dat.h"
#include	"fns.h"
#include	"error.h"
#include	<a.out.h>
#include	<dynld.h>

/*
 * TO DO
 *	- dynamic allocation of Dev.dc
 *	- inter-module dependencies
 *	- reference count on Dev to allow error("inuse") [or how else to do it]
 *	- is Dev.shutdown the right function to call?  Dev.config perhaps?
 */

#define	DBG	if(1) print

extern ulong ndevs;

enum
{
	Qdir,
	Qdynld,
	Qdynsyms,

	DEVCHAR	= 'L',
};

static Dirtab	dltab[] =
{
	".",			{Qdir, 0, QTDIR},	0,	DMDIR|0555,
	"dynld",		{Qdynld},	0,	0644,
	"dynsyms",	{Qdynsyms},	0,	0444,
};

typedef struct Dyndev Dyndev;

struct Dyndev
{
	char*	name;	/* device name (eg, "dynld") */
	char*	tag;	/* version tag (eg, MD5 or SHA1 hash of content) */
	char*	path;	/* file from whence it came */
	Dynobj*	o;
	Dev*	dev;
	Dyndev*	next;
};

static	Dyndev	*loaded;
static	QLock	dllock;

static	Dyndev**	finddyndev(char*);
static	int	matched(Dyndev*, char*, char*);

extern	Dynobj*	kdynloadfd(int, Dynsym*, int, ulong);

static void
dlfree(Dyndev *l)
{
	if(l != nil){
		free(l->tag);
		free(l->name);
		free(l->path);
		dynobjfree(l->o);
		free(l);
	}
}

static Dyndev*
dlload(char *path, Dynsym *tab, int ntab)
{
	Dyndev *l;
	int fd;

	/* in Plan 9, would probably use Chan* interface here */
	fd = kopen(path, OREAD);
	if(fd < 0)
		error("cannot open");
	if(waserror()){
		kclose(fd);
		nexterror();
	}
	l = mallocz(sizeof(Dyndev), 1);
	if(l == nil)
		error(Enomem);
	if(waserror()){
		dlfree(l);
		nexterror();
	}
	l->path = strdup(path);
	if(l->path == nil)
		error(Enomem);
	l->o = kdynloadfd(fd, tab, ntab, 0);
	if(l->o == nil)
		error(up->env->errstr);
	poperror();
	poperror();
	kclose(fd);
	return l;
}

static void
devload(char *name, char *path, char *tag)
{
	int i;
	Dyndev *l, **lp;
	Dev *dev;
	char tabname[32];

	lp = finddyndev(name);
	if(*lp != nil)
		error("already loaded");	/* i'm assuming the name (eg, "cons") is to be unique */
	l = dlload(path, _exporttab, dyntabsize(_exporttab));
	if(waserror()){
		dlfree(l);
		nexterror();
	}
	snprint(tabname, sizeof(tabname), "%sdevtab", name);
	dev = dynimport(l->o, tabname, signof(*dev));
	if(dev == nil)
		errorf("can't find %sdevtab in module", name);
	kstrdup(&l->name, name);
	kstrdup(&l->tag, tag != nil? tag: "");
	if(dev->name == nil)
		dev->name = l->name;
	else if(strcmp(dev->name, l->name) != 0)
		errorf("module file has device %s", dev->name);
	/* TO DO: allocate dev->dc dynamically (cf. brucee's driver) */
	if(devno(dev->dc, 1) >= 0)
		errorf("devchar %C already used", dev->dc);
	for(i = 0; devtab[i] != nil; i++)
		;
	if(i >= ndevs || devtab[i+1] != nil)
		error("device table full");
#ifdef NATIVE
	i = splhi();
	dev->reset();
	splx(i);
#endif
	dev->init();
	l->dev = devtab[i] = dev;
	l->next = loaded;
	loaded = l;			/* recently loaded ones first: good unload order? */
	poperror();
}

static Dyndev**
finddyndev(char *name)
{
	Dyndev *l, **lp;

	for(lp = &loaded; (l = *lp) != nil; lp = &l->next)
		if(strcmp(l->name, name) == 0)
			break;
	return lp;
}

static int
matched(Dyndev *l, char *path, char *tag)
{
	if(path != nil && strcmp(l->path, path) != 0)
		return 0;
	if(tag != nil && strcmp(l->tag, tag) != 0)
		return 0;
	return 1;
}

static void
devunload(char *name, char *path, char *tag)
{
	int i;
	Dyndev *l, **lp;

	lp = finddyndev(name);
	l = *lp;
	if(l == nil)
		error("not loaded");
	if(!matched(l, path, tag))
		error("path/tag mismatch");
	for(i = 0; i < ndevs; i++)
		if(l->dev == devtab[i]){
			devtab[i] = nil;		/* TO DO: ensure driver is not currently in use */
			break;
		}
#ifdef NATIVE
	l->dev->shutdown();
#endif
	*lp = l->next;
	dlfree(l);
}

static long
readdynld(void *a, ulong n, ulong offset)
{
	char *p;
	Dyndev *l;
	int m, len;

	m = 0;
	for(l = loaded; l != nil; l = l->next)
		m += 48 + strlen(l->name) + strlen(l->path) + strlen(l->tag);
	p = malloc(m);
	if(p == nil)
		error(Enomem);
	if(waserror()){
		free(p);
		nexterror();
	}
	*p = 0;
	len = 0;
	for(l = loaded; l != nil; l = l->next)
		if(l->dev)
			len += snprint(p+len, m-len, "#%C\t%.8p\t%.8lud\t%q\t%q\t%q\n",
					l->dev->dc, l->o->base, l->o->size, l->name, l->path, l->tag);
	n = readstr(offset, a, n, p);
	poperror();
	free(p);
	return n;
}

static long
readsyms(char *a, ulong n, ulong offset)
{
	char *p;
	Dynsym *t;
	long l, nr;

	p = malloc(READSTR);
	if(p == nil)
		error(Enomem);
	if(waserror()){
		free(p);
		nexterror();
	}
	nr = 0;
	for(t = _exporttab; n > 0 && t->name != nil; t++){
		l = snprint(p, READSTR, "%.8lux %.8lux %s\n", t->addr, t->sig, t->name);
		if(offset >= l){
			offset -= l;
			continue;
		}
		l = readstr(offset, a, n, p);
		offset = 0;
		n -= l;
		a += l;
		nr += l;
	}
	poperror();
	free(p);
	return nr;
}

static Chan*
dlattach(char *spec)
{
	return devattach(DEVCHAR, spec);
}

static Walkqid*
dlwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, dltab, nelem(dltab), devgen);
}

static int
dlstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, dltab, nelem(dltab), devgen);
}

static Chan*
dlopen(Chan *c, int omode)
{
	return devopen(c, omode, dltab, nelem(dltab), devgen);
}

static void
dlclose(Chan *c)
{
	USED(c);
}

static long
dlread(Chan *c, void *a, long n, vlong voffset)
{
	switch((ulong)c->qid.path){
	case Qdir:
		return devdirread(c, a, n, dltab, nelem(dltab), devgen);
	case Qdynld:
		return readdynld(a, n, voffset);
	case Qdynsyms:
		return readsyms(a, n, voffset);
	default:
		error(Egreg);
	}
	return n;
}

static long
dlwrite(Chan *c, void *a, long n, vlong voffset)
{
	Cmdbuf *cb;
	char *name, *tag, *path;

	USED(voffset);
	switch((ulong)c->qid.path){
	case Qdynld:
		cb = parsecmd(a, n);
		qlock(&dllock);
		if(waserror()){
			qunlock(&dllock);
			free(cb);
			nexterror();
		}
		if(cb->nf < 3 || strcmp(cb->f[1], "dev") != 0)	/* only do devices */
			cmderror(cb, Ebadctl);
		name = cb->f[2];
		path = nil;
		if(cb->nf > 3)
			path = cb->f[3];
		tag = nil;
		if(cb->nf > 4)
			tag = cb->f[4];
		if(strcmp(cb->f[0], "load") == 0){
			if(path == nil)
				cmderror(cb, "missing load path");
			devload(name, path, tag);	/* TO DO: remaining parameters might be dependencies? */
		}else if(strcmp(cb->f[0], "unload") == 0)
			devunload(name, path, tag);
		else
			cmderror(cb, Ebadctl);
		poperror();
		qunlock(&dllock);
		free(cb);
		break;
	default:
		error(Egreg);
	}
	return n;
}

Dev dynlddevtab = {
	DEVCHAR,
	"dynld",

	devinit,
	dlattach,
	dlwalk,
	dlstat,
	dlopen,
	devcreate,
	dlclose,
	dlread,
	devbread,
	dlwrite,
	devbwrite,
	devremove,
	devwstat,
};
