jehanne/sys/src/kern/port/devsegment.c

596 lines
10 KiB
C

/*
* 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.
*/
/* Portions of this file are Copyright (C) 2015-2018 Giacomo Tesio <giacomo@tesio.it>
* See /doc/license/gpl-2.0.txt for details about the licensing.
*/
/* Portions of this file are Copyright (C) 9front's team.
* See /doc/license/9front-mit for details about the licensing.
* See http://git.9front.org/plan9front/plan9front/HEAD/info.html for a list of authors.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
enum
{
Qtopdir,
Qsegdir,
Qctl,
Qdata,
/* commands to kproc */
Cnone=0,
Cread,
Cwrite,
Cstart,
Cdie,
};
#define TYPE(x) (int)( (c)->qid.path & 0x7 )
#define SEG(x) ( ((c)->qid.path >> 3) & 0x3f )
#define PATH(s, t) ( ((s)<<3) | (t) )
typedef struct Globalseg Globalseg;
struct Globalseg
{
Ref;
Segment *s;
char *name;
char *uid;
int64_t length;
long perm;
/* kproc to do reading and writing */
QLock l; /* sync kproc access */
Rendez cmdwait; /* where kproc waits */
Rendez replywait; /* where requestor waits */
Proc *kproc;
char *data;
long off;
int dlen;
int cmd;
char err[64];
};
//static Globalseg *globalseg[100];
//static Lock globalseglock;
Segment* (*_globalsegattach)(Proc*, char*);
static Segment* globalsegattach(Proc *p, char *name);
static int cmddone(void*);
static void segmentkproc(void*);
static void docmd(Globalseg *g, int cmd);
/*
* returns with globalseg incref'd
*/
static Globalseg*
getgseg(Chan *c)
{
int x;
Globalseg *g;
x = SEG(c);
lock(&globalseglock);
if(x >= nelem(globalseg))
panic("getgseg");
g = globalseg[x];
if(g != nil)
incref(g);
unlock(&globalseglock);
if(g == nil)
error("global segment disappeared");
return g;
}
static void
putgseg(Globalseg *g)
{
if(decref(g) > 0)
return;
if(g->s != nil)
putseg(g->s);
if(g->kproc)
docmd(g, Cdie);
jehanne_free(g->name);
jehanne_free(g->uid);
jehanne_free(g);
}
static int
segmentgen(Chan *c, char* _1, Dirtab* _2, int _3, int s, Dir *dp)
{
Qid q;
Globalseg *g;
uint32_t size;
switch(TYPE(c)) {
case Qtopdir:
if(s == DEVDOTDOT){
q.vers = 0;
q.path = PATH(0, Qtopdir);
q.type = QTDIR;
devdir(c, q, "#g", 0, eve, DMDIR|0777, dp);
break;
}
if(s >= nelem(globalseg))
return -1;
lock(&globalseglock);
g = globalseg[s];
if(g == nil){
unlock(&globalseglock);
return 0;
}
q.vers = 0;
q.path = PATH(s, Qsegdir);
q.type = QTDIR;
devdir(c, q, g->name, 0, g->uid, DMDIR|0777, dp);
unlock(&globalseglock);
break;
case Qsegdir:
if(s == DEVDOTDOT){
q.vers = 0;
q.path = PATH(0, Qtopdir);
q.type = QTDIR;
devdir(c, q, "#g", 0, eve, DMDIR|0777, dp);
break;
}
/* fall through */
case Qctl:
case Qdata:
switch(s){
case 0:
g = getgseg(c);
q.vers = 0;
q.path = PATH(SEG(c), Qctl);
q.type = QTFILE;
devdir(c, q, "ctl", 0, g->uid, g->perm, dp);
putgseg(g);
break;
case 1:
g = getgseg(c);
q.vers = 0;
q.path = PATH(SEG(c), Qdata);
q.type = QTFILE;
if(g->s != nil)
size = g->s->top - g->s->base;
else
size = 0;
devdir(c, q, "data", size, g->uid, g->perm, dp);
putgseg(g);
break;
default:
return -1;
}
break;
}
return 1;
}
static void
segmentinit(void)
{
_globalsegattach = globalsegattach;
}
static Chan*
segmentattach(Chan *c, Chan *ac, char *spec, int flags)
{
return devattach('g', spec);
}
static Walkqid*
segmentwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, 0, 0, segmentgen);
}
static long
segmentstat(Chan *c, uint8_t *db, long n)
{
return devstat(c, db, n, 0, 0, segmentgen);
}
static int
cmddone(void *arg)
{
Globalseg *g = arg;
return g->cmd == Cnone;
}
static Chan*
segmentopen(Chan *c, int omode)
{
Globalseg *g;
switch(TYPE(c)){
case Qtopdir:
case Qsegdir:
if(omode != OREAD)
error(Eisdir);
break;
case Qctl:
g = getgseg(c);
if(waserror()){
putgseg(g);
nexterror();
}
devpermcheck(g->uid, g->perm, omode);
c->aux = g;
poperror();
c->flag |= COPEN;
break;
case Qdata:
g = getgseg(c);
if(waserror()){
putgseg(g);
nexterror();
}
devpermcheck(g->uid, g->perm, omode);
if(g->s == nil)
error("segment not yet allocated");
if(g->kproc == nil){
qlock(&g->l);
if(waserror()){
qunlock(&g->l);
nexterror();
}
if(g->kproc == nil){
g->cmd = Cnone;
kproc(g->name, segmentkproc, g);
docmd(g, Cstart);
}
qunlock(&g->l);
poperror();
}
c->aux = g;
poperror();
c->flag |= COPEN;
break;
default:
panic("segmentopen");
}
c->mode = openmode(omode);
c->offset = 0;
return c;
}
static void
segmentclose(Chan *c)
{
if(TYPE(c) == Qtopdir)
return;
if(c->flag & COPEN)
putgseg(c->aux);
}
static Chan*
segmentcreate(Chan *c, char *name, int omode, int perm)
{
int x, xfree;
Globalseg *g;
if(TYPE(c) != Qtopdir)
error(Eperm);
if(isphysseg(name))
error(Eexist);
if((perm & DMDIR) == 0)
error(Ebadarg);
if(waserror()){
unlock(&globalseglock);
nexterror();
}
lock(&globalseglock);
xfree = -1;
for(x = 0; x < nelem(globalseg); x++){
g = globalseg[x];
if(g == nil){
if(xfree < 0)
xfree = x;
} else {
if(jehanne_strcmp(g->name, name) == 0)
error(Eexist);
}
}
if(xfree < 0)
error("too many global segments");
g = smalloc(sizeof(Globalseg));
g->ref = 1;
kstrdup(&g->name, name);
kstrdup(&g->uid, up->user);
g->perm = 0660;
globalseg[xfree] = g;
unlock(&globalseglock);
poperror();
c->qid.path = PATH(x, Qsegdir);
c->qid.type = QTDIR;
c->qid.vers = 0;
c->mode = openmode(omode);
c->mode = OWRITE;
return c;
}
static long
segmentread(Chan *c, void *a, long n, int64_t voff)
{
Globalseg *g;
char buf[32];
if(c->qid.type == QTDIR)
return devdirread(c, a, n, (Dirtab *)0, 0L, segmentgen);
switch(TYPE(c)){
case Qctl:
g = c->aux;
if(g->s == nil)
error("segment not yet allocated");
jehanne_snprint(buf, sizeof buf, "va %#lux %#lux\n", g->s->base,
g->s->top-g->s->base);
return readstr(voff, a, n, buf);
case Qdata:
g = c->aux;
if(voff > g->s->top - g->s->base)
error(Ebadarg);
if(voff + n > g->s->top - g->s->base)
n = g->s->top - g->s->base - voff;
qlock(&g->l);
g->off = voff + g->s->base;
g->data = smalloc(n);
if(waserror()){
jehanne_free(g->data);
qunlock(&g->l);
nexterror();
}
g->dlen = n;
docmd(g, Cread);
jehanne_memmove(a, g->data, g->dlen);
jehanne_free(g->data);
qunlock(&g->l);
poperror();
return g->dlen;
default:
panic("segmentread");
}
return 0; /* not reached */
}
static long
segmentwrite(Chan *c, void *a, long n, int64_t voff)
{
Cmdbuf *cb;
Globalseg *g;
uintptr_t va, len, top;
if(c->qid.type == QTDIR)
error(Eperm);
switch(TYPE(c)){
case Qctl:
g = c->aux;
cb = parsecmd(a, n);
if(jehanne_strcmp(cb->f[0], "va") == 0){
if(g->s != nil)
error("already has a virtual address");
if(cb->nf < 3)
error(Ebadarg);
va = jehanne_strtoull(cb->f[1], 0, 0);
len = jehanne_strtoull(cb->f[2], 0, 0);
top = ROUNDUP(va + len, PGSZ);
va = va&~(PGSZ-1);
len = (top - va) / PGSZ;
if(len == 0)
error(Ebadarg);
g->s = newseg(SG_SHARED, va, top, nil, 0);
} else
error(Ebadctl);
break;
case Qdata:
g = c->aux;
if(voff + n > g->s->top - g->s->base)
error(Ebadarg);
qlock(&g->l);
g->off = voff + g->s->base;
g->data = smalloc(n);
if(waserror()){
jehanne_free(g->data);
qunlock(&g->l);
nexterror();
}
g->dlen = n;
jehanne_memmove(g->data, a, g->dlen);
docmd(g, Cwrite);
jehanne_free(g->data);
qunlock(&g->l);
poperror();
return g->dlen;
default:
panic("segmentwrite");
}
return 0; /* not reached */
}
static long
segmentwstat(Chan *c, uint8_t *dp, long n)
{
Globalseg *g;
Dir *d;
if(c->qid.type == QTDIR)
error(Eperm);
g = getgseg(c);
if(waserror()){
putgseg(g);
nexterror();
}
if(jehanne_strcmp(g->uid, up->user) && !iseve())
error(Eperm);
d = smalloc(sizeof(Dir)+n);
n = jehanne_convM2D(dp, n, &d[0], (char*)&d[1]);
g->perm = d->mode & 0777;
putgseg(g);
poperror();
jehanne_free(d);
return n;
}
static void
segmentremove(Chan *c)
{
Globalseg *g;
int x;
if(TYPE(c) != Qsegdir)
error(Eperm);
lock(&globalseglock);
x = SEG(c);
g = globalseg[x];
globalseg[x] = nil;
unlock(&globalseglock);
if(g != nil)
putgseg(g);
}
/*
* called by on segment attach
*/
static Segment*
globalsegattach(Proc *p, char *name)
{
int x;
Globalseg *g;
Segment *s;
g = nil;
if(waserror()){
unlock(&globalseglock);
nexterror();
}
lock(&globalseglock);
for(x = 0; x < nelem(globalseg); x++){
g = globalseg[x];
if(g != nil && jehanne_strcmp(g->name, name) == 0)
break;
}
if(x == nelem(globalseg)){
unlock(&globalseglock);
poperror();
return nil;
}
devpermcheck(g->uid, g->perm, ORDWR);
s = g->s;
if(s == nil)
error("global segment not assigned a virtual address");
if(isoverlap(p, s->base, s->top - s->base) != nil)
error("overlaps existing segment");
incref(&s->r);
unlock(&globalseglock);
poperror();
return s;
}
static void
docmd(Globalseg *g, int cmd)
{
g->err[0] = 0;
g->cmd = cmd;
wakeup(&g->cmdwait);
sleep(&g->replywait, cmddone, g);
if(g->err[0])
error(g->err);
}
static int
cmdready(void *arg)
{
Globalseg *g = arg;
return g->cmd != Cnone;
}
static void
segmentkproc(void *arg)
{
Globalseg *g = arg;
int done;
int sno;
for(sno = 0; sno < NSEG; sno++)
if(up->seg[sno] == nil && sno != ESEG)
break;
if(sno == NSEG)
panic("segmentkproc");
g->kproc = up;
incref(&g->s->r);
up->seg[sno] = g->s;
for(done = 0; !done;){
sleep(&g->cmdwait, cmdready, g);
if(waserror()){
jehanne_strncpy(g->err, up->errstr, sizeof(g->err));
} else {
switch(g->cmd){
case Cstart:
break;
case Cdie:
done = 1;
break;
case Cread:
jehanne_memmove(g->data, (char*)g->off, g->dlen);
break;
case Cwrite:
jehanne_memmove((char*)g->off, g->data, g->dlen);
break;
}
poperror();
}
g->cmd = Cnone;
wakeup(&g->replywait);
}
}
Dev segmentdevtab = {
'g',
"segment",
devreset,
segmentinit,
devshutdown,
segmentattach,
segmentwalk,
segmentstat,
segmentopen,
segmentcreate,
segmentclose,
segmentread,
devbread,
segmentwrite,
devbwrite,
segmentremove,
segmentwstat,
};