summaryrefslogtreecommitdiff
path: root/os/port/devdynld.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/port/devdynld.c')
-rw-r--r--os/port/devdynld.c365
1 files changed, 365 insertions, 0 deletions
diff --git a/os/port/devdynld.c b/os/port/devdynld.c
new file mode 100644
index 00000000..cc53308c
--- /dev/null
+++ b/os/port/devdynld.c
@@ -0,0 +1,365 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include <a.out.h>
+#include <dynld.h>
+#include <kernel.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
+#define NATIVE
+
+
+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",
+
+ devreset,
+ devinit,
+ devshutdown, /* TO DO */
+ dlattach,
+ dlwalk,
+ dlstat,
+ dlopen,
+ devcreate,
+ dlclose,
+ dlread,
+ devbread,
+ dlwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};