shithub: riscv

Download patch

ref: 2da5e135dc5d1d50ac2a760fe1bc693b6008087c
parent: a2d8dcfd8235547ca1e14dcca42de70e06a0ec96
author: aiju <devnull@localhost>
date: Wed Mar 7 05:06:18 EST 2018

add nusb/cam

--- /dev/null
+++ b/sys/src/cmd/nusb/cam/cam.c
@@ -1,0 +1,374 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <bio.h>
+#include "usb.h"
+#include "uvc.h"
+#include "dat.h"
+#include "fns.h"
+
+char user[] = "cam";
+
+void printVCHeader(void *vp);
+void printVCInputTerminal(void *vp);
+void printVCOutputTerminal(void *vp);
+void printVCCameraTerminal(void *vp);
+void printVCSelectorUnit(void *vp);
+void printVCProcessingUnit(void *vp);
+void printVCEncodingUnit(void *vp);
+void printVCExtensionUnit(void *vp);
+void printVSInputHeader(void *vp);
+void printVSOutputHeader(void *vp);
+void printVSStillFrame(void *vp);
+void printVSUncompressedFormat(void *vp);
+void printVSUncompressedFrame(void *vp);
+void printVSColorFormat(void *vp);
+void printProbeControl(void *vp);
+
+Cam *cams;
+int nunit;
+VCUnit **unit;
+Iface **unitif;
+
+int debug;
+
+int
+getframedesc(Cam *c, int i, int j, Format **fp, VSUncompressedFrame **gp)
+{
+	Format *f;
+
+	if(i >= c->nformat) return -1;
+	f = c->format[i];
+	if(f == nil) return -1;
+	if(j >= f->nframe) return -1;
+	if(f->frame[j] == nil) return -1;
+	if(fp != nil) *fp = f;
+	if(gp != nil) *gp = f->frame[j];
+	return 0;
+}
+
+static void
+parsevcdesc(Dev *dev, Desc *d)
+{
+	VCDescriptor *vdp;
+	static Cam *cam;
+	static Format *format;
+	int i;
+	Format *f;
+	
+	if(d == nil) return;
+	vdp = (VCDescriptor *) &d->data;
+	if(vdp->bDescriptorType != 0x24) return;
+	if(Class(d->iface->csp) != CC_VIDEO) return;
+	switch(Subclass(d->iface->csp)){
+	case SC_VIDEOSTREAMING:
+		switch(vdp->bDescriptorSubtype){
+		case VS_INPUT_HEADER:
+			format = nil;
+			cam = emallocz(sizeof(Cam), 1);
+			cam->dev = dev;
+			cam->iface = d->iface;
+			cam->hdr = (VSInputHeader *) vdp;
+			cam->next = cams;
+			cams = cam;
+			break;
+		case VS_FORMAT_UNCOMPRESSED:
+			if(cam == nil) return;
+			f = emallocz(sizeof(Format), 1);
+			f->desc = (void*)vdp;
+			i = f->desc->bFormatIndex;
+			if(i >= cam->nformat){
+				cam->format = realloc(cam->format, (i + 1) * sizeof(void *));
+				memset(cam->format + cam->nformat, 0, (i - cam->nformat) * sizeof(void *));
+				cam->nformat = i + 1;
+			}
+			cam->format[i] = f;
+			if(format == nil) cam->pc.bFormatIndex = i;
+			format = f;
+			break;
+		case VS_FRAME_UNCOMPRESSED:
+			if(cam == nil || cam->nformat == 0) return;
+			f = format;
+			i = ((VSUncompressedFrame*)vdp)->bFrameIndex;
+			if(i >= f->nframe){
+				f->frame = realloc(f->frame, (i + 1) * sizeof(void *));
+				memset(f->frame + f->nframe, 0, (i - f->nframe) * sizeof(void *));
+				f->nframe = i + 1;
+			}
+			f->frame[i] = (void*)vdp;
+			break;
+		}
+		break;
+	case SC_VIDEOCONTROL:
+		switch(vdp->bDescriptorSubtype){
+		case VC_INPUT_TERMINAL:
+		case VC_OUTPUT_TERMINAL:
+		case VC_SELECTOR_UNIT:
+		case VC_PROCESSING_UNIT:
+		case VC_ENCODING_UNIT:
+		case VC_EXTENSION_UNIT:
+			i = ((VCUnit*)vdp)->bUnitID;
+			if(i >= nunit){
+				unit = realloc(unit, (i + 1) * sizeof(void *));
+				unitif = realloc(unitif, (i + 1) * sizeof(Iface *));
+				memset(unit + nunit, 0, (i - nunit) * sizeof(void *)); 
+				memset(unitif + nunit, 0, (i - nunit) * sizeof(void *));
+				nunit = i + 1;
+			}
+			unit[i] = (void*)vdp;
+			unitif[i] = d->iface;
+			break;
+		}
+		break;
+	}
+}
+
+static void
+createfiles(File *root, char *devname, Cam *c)
+{
+	char buf[512];
+	File *d;
+	
+	snprint(buf, sizeof(buf), "cam%s.%d", devname, c->iface->id);
+	d = createfile(root, buf, user, DMDIR|0555, c);
+	c->ctlfile = createfile(d, "ctl", user, 0666, c);
+	c->formatsfile = createfile(d, "formats", user, 0444, c);
+	c->videofile = createfile(d, "video", user, 0444, c);
+	c->framefile = createfile(d, "frame", user, 0444, c);
+	c->descfile = createfile(d, "desc", user, 0444, c);
+}
+
+static char *
+formatread(Cam *c)
+{
+	int i, j, k;
+	Fmt fmt;
+	Format *f;
+	VSUncompressedFrame *g;
+	char buf[5];
+	
+	fmtstrinit(&fmt);
+	for(i = 0; i < c->nformat; i++){
+		f = c->format[i];
+		if(f == nil) continue;
+		memcpy(buf, f->desc->guidFormat, 4);
+		buf[4] = 0;
+		for(j = 0; j < f->nframe; j++){
+			if((g = f->frame[j]) == nil) continue;
+			fmtprint(&fmt, "%dx%dx%d-%s ", GET2(g->wWidth), GET2(g->wHeight), f->desc->bBitsPerPixel, buf);
+			if(g->bFrameIntervalType == 0)
+				fmtprint(&fmt, "%.2f-%.2f\n", 10e6 / (u32int)GET4(g->dwFrameInterval[0]), 10e6 / (u32int)GET4(g->dwFrameInterval[1]));
+			else
+				for(k = 0; k < g->bFrameIntervalType; k++)
+					fmtprint(&fmt, "%.2f%c", 10e6 / (u32int)GET4(g->dwFrameInterval[k]), k == g->bFrameIntervalType - 1 ? '\n' : ',');
+		}
+	}
+	return fmtstrflush(&fmt);
+}
+
+static char *
+descread(Cam *c)
+{
+	Fmt fmt;
+	int i;
+	Usbdev *ud;
+	Desc *d;
+	VCDescriptor *vdp;
+
+	ud = c->dev->usb;
+	fmtstrinit(&fmt);
+	for(i = 0; i < nelem(ud->ddesc); i++){
+		d = ud->ddesc[i];
+		if(d == nil) continue;
+		vdp = (VCDescriptor *) &d->data;
+		if(vdp->bDescriptorType != 0x24) continue;
+		if(Class(d->iface->csp) != CC_VIDEO) continue;
+		printDescriptor(&fmt, d->iface, vdp);
+	}
+	return fmtstrflush(&fmt);
+}
+
+typedef struct ReadState ReadState;
+struct ReadState {
+	int len;
+	char *buf;
+};
+
+static void
+strread(Req *req, char *str, int len)
+{
+	ReadState *rs;
+	
+	if(str == nil)
+		return;
+	rs = emallocz(sizeof(ReadState), 1);
+	rs->len = len < 0 ? strlen(str) : len;
+	rs->buf = str;
+	req->fid->aux = rs;
+}
+
+static void
+fsread(Req *req)
+{
+	File *f;
+	Cam *c;
+	ReadState *rs;
+
+	if(req->fid == nil || req->fid->file == nil || req->fid->file->aux == nil){
+		respond(req, "the front fell off");
+		return;
+	}
+	f = req->fid->file;
+	c = f->aux;
+	if(req->fid->aux == nil)
+		if(f == c->formatsfile)
+			strread(req, formatread(c), -1);
+		else if(f == c->ctlfile)
+			strread(req, ctlread(c), -1);
+		else if(f == c->descfile)
+			strread(req, descread(c), -1);
+		else if(f == c->videofile || f == c->framefile){
+			videoread(req, c, 1);
+			return;
+		}
+	if((rs = req->fid->aux) == nil){
+		respond(req, "the front fell off");
+		return;
+	}
+	readbuf(req, rs->buf, rs->len);
+	respond(req, nil);
+}
+
+static void
+fswrite(Req *req)
+{
+	File *f;
+	Cam *c;
+	char *s;
+	int n;
+
+	if(req->fid == nil || req->fid->file == nil || req->fid->file->aux == nil){
+err:		respond(req, "the front fell off");
+		return;
+	}
+	f = req->fid->file;
+	c = f->aux;
+	if(f != c->ctlfile)
+		goto err;
+	for(n = 0; n < req->ifcall.count; n++)
+		if(req->ifcall.data[n] == '\n')
+			break;
+	s = emallocz(n+1, 0);
+	memcpy(s, req->ifcall.data, n);
+	s[n] = 0;
+	werrstr("invalid argument");
+	if(ctlwrite(c, s) < 0)
+		responderror(req);
+	else
+		respond(req, nil);
+	free(s);
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+	ReadState *rs;
+
+	rs = fid->aux;
+	if(rs != nil){
+		free(rs->buf);
+		free(rs);
+	}
+	if(fid->file != nil && fid->file->aux != nil && (fid->file == ((Cam*)fid->file->aux)->videofile || fid->file == ((Cam*)fid->file->aux)->framefile))
+		videoclose(fid->file->aux);
+}
+
+static void
+fsopen(Req *req)
+{
+	File *f;
+	Cam *c;
+
+	if((req->ofcall.qid.type & QTDIR) != 0){
+		respond(req, nil);
+		return;
+	}
+	if(req->fid == nil || req->fid->file == nil || req->fid->file->aux == nil){
+		respond(req, "the front fell off");
+		return;
+	}
+	f = req->fid->file;
+	c = f->aux;
+	if(f == c->videofile || f == c->framefile)
+		if(videoopen(c, f == c->framefile) < 0){
+			responderror(req);
+			return;
+		}
+	respond(req, nil);
+}
+
+static void
+fsflush(Req *req)
+{
+	if(req->oldreq->fid->file != nil && req->oldreq->fid->file->aux != nil && (((Cam*)req->oldreq->fid->file->aux)->videofile == req->oldreq->fid->file || ((Cam*)req->oldreq->fid->file->aux)->framefile == req->oldreq->fid->file))
+		videoflush(req->oldreq, req->oldreq->fid->file->aux);
+	respond(req, nil);
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-d] devid\n", argv0);
+	threadexits("usage");
+}
+
+static Srv fs = {
+	.open = fsopen,
+	.read = fsread,
+	.write = fswrite,
+	.flush = fsflush,
+	.destroyfid = fsdestroyfid,
+};
+
+void
+threadmain(int argc, char* argv[])
+{
+	int i;
+	Dev *d;
+	Usbdev *ud;
+	char buf[512];
+	Cam *c;
+
+	ARGBEGIN{
+	case 'd':
+		debug++;
+		break;
+	default:
+		usage();
+	}ARGEND;
+	if(argc != 1)
+		usage();
+	d = getdev(*argv);
+	if(d == nil)
+		sysfatal("getdev: %r");
+	ud = d->usb;
+	for(i = 0; i < nelem(ud->ddesc); i++)
+		parsevcdesc(d, ud->ddesc[i]);
+	if(cams == nil)
+		sysfatal("no input streams");
+	for(c = cams; c != nil; c = c->next)
+		if(c->nformat > 0){
+			c->pc.bFrameIndex = c->format[c->pc.bFormatIndex]->desc->bDefaultFrameIndex;
+			if(c->pc.bFrameIndex < c->format[c->pc.bFormatIndex]->nframe && c->format[c->pc.bFormatIndex]->frame[c->pc.bFrameIndex] != nil)
+				PUT4(c->pc.dwFrameInterval, GET4(c->format[c->pc.bFormatIndex]->frame[c->pc.bFrameIndex]->dwDefaultFrameInterval));
+		}
+	fs.tree = alloctree(user, "usb", DMDIR|0555, nil);
+	for(c = cams; c != nil; c = c->next)
+		createfiles(fs.tree->root, d->hname, c);
+	snprint(buf, sizeof buf, "%d.cam", d->id);
+	threadpostsharesrv(&fs, nil, "usb", buf);
+	threadexits(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/cam/ctl.c
@@ -1,0 +1,532 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "usb.h"
+#include "uvc.h"
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Param Param;
+
+enum {
+	PARAMSPEC,
+	PARAMCT,
+	PARAMPU,
+};
+
+struct Param {
+	char *name;
+	int type;
+	int cs;
+	int len;
+	int flag;
+	char *(*read)(Cam *, int, Param *);
+	int (*write)(Cam *, int, Param *, char **, int);
+	void *auxp;
+	int auxi;
+};
+
+void
+errorcode(Dev *d, int term)
+{
+	uchar val;
+	char *str[] = {
+		"No error",
+		"Not ready",
+		"Wrong state",
+		"Power",
+		"Out of range",
+		"Invalid unit",
+		"Invalid control",
+		"Invalid Request",
+		"Invalid value within range"
+	};
+	if(usbcmd(d, 0xA1, GET_CUR, VC_REQUEST_ERROR_CODE_CONTROL << 8, (uchar)term, &val, 1) <= 0)
+		return;
+	if(val < nelem(str))
+		werrstr("%s", str[val]);
+}
+
+int
+infocheck(Cam *c, int term, Param *p)
+{
+	uchar val;
+
+	if(usbcmd(c->dev, 0xA1, GET_INFO, p->cs << 8, term, &val, 1) <= 0){ errorcode(c->dev, term); return -1; }
+	if((val & 1) == 0){
+		werrstr("GET not supported");
+		return -1;
+	}
+	return 0;
+}
+
+char *
+pboolread(Cam *c, int term, Param *p)
+{
+	uchar val;
+	Dev *d;
+
+	d = c->dev;
+	if(infocheck(c, term, p) < 0) return nil;
+	if(usbcmd(d, 0xA1, GET_CUR, p->cs << 8, term, &val, 1) <= 0){ errorcode(d, term); return nil; }
+	if(val)
+		return strdup("true");
+	return strdup("false");
+}
+
+int
+pboolwrite(Cam *c, int term, Param *p, char **f, int nf)
+{
+	uchar v0, v1;
+
+	if(nf != 1)
+		return -1;
+	v0 = cistrcmp(f[0], "false") == 0 || cistrcmp(f[0], "0") == 0 || cistrcmp(f[0], "no") == 0;
+	v1 = cistrcmp(f[0], "true") == 0 || cistrcmp(f[0], "1") == 0 || cistrcmp(f[0], "yes") == 0;
+	if(!(v0 ^ v1))
+		return -1;
+	if(usbcmd(c->dev, 0x21, SET_CUR, p->cs << 8, term, &v1, 1) <= 0){ errorcode(c->dev, term); return -1; }
+	return 0;
+}
+
+char *
+pintread(Cam *c, int term, Param *p)
+{
+	uchar cur[4], min[4], max[4], res[4];
+	Dev *d;
+
+	d = c->dev;
+	if(infocheck(c, term, p) < 0) return nil;
+	if(usbcmd(d, 0xA1, GET_CUR, p->cs << 8, term, cur, p->len) < p->len){ errorcode(d, term); return nil; }
+	if(usbcmd(d, 0xA1, GET_MIN, p->cs << 8, term, min, p->len) < p->len){ errorcode(d, term); return nil; }
+	if(usbcmd(d, 0xA1, GET_RES, p->cs << 8, term, res, p->len) < p->len){ errorcode(d, term); return nil; }
+	if(usbcmd(d, 0xA1, GET_MAX, p->cs << 8, term, max, p->len) < p->len){ errorcode(d, term); return nil; }
+	switch(p->len){
+	case 1: return smprint("%d %d/%d/%d", (char)cur[0], (char)min[0], (char)res[0], (char)max[0]);
+	case 2: return smprint("%d %d/%d/%d", (short)GET2(cur), (short)GET2(min), (short)GET2(res), (short)GET2(max));
+	case 4: return smprint("%d %d/%d/%d", (int)GET4(cur), (int)GET4(min), (int)GET4(res), (int)GET4(max));
+	}
+	werrstr("pintread: unimplemented length %d", p->len);
+	return nil;
+}
+
+int
+pintwrite(Cam *c, int term, Param *p, char **f, int nf)
+{
+	int v;
+	char *sp;
+	uchar buf[4];
+	
+	if(nf != 1) return -1;
+	v = strtol(f[0], &sp, 0);
+	if(*f[0] == 0 || *sp != 0) return -1;
+	buf[0] = v;
+	buf[1] = v >> 8;
+	buf[2] = v >> 16;
+	buf[3] = v >> 24;
+	if(usbcmd(c->dev, 0x21, SET_CUR, p->cs << 8, term, buf, p->len) < p->len){ errorcode(c->dev, term); return -1; }
+	return 0;
+}
+
+char *
+puintread(Cam *c, int term, Param *p)
+{
+	uchar cur[4], min[4], max[4], res[4];
+	Dev *d;
+
+	d = c->dev;
+	if(infocheck(c, term, p) < 0) return nil;
+	if(usbcmd(d, 0xA1, GET_CUR, p->cs << 8, term, cur, p->len) < p->len){ errorcode(d, term); return nil; }
+	if(usbcmd(d, 0xA1, GET_MIN, p->cs << 8, term, min, p->len) < p->len){ errorcode(d, term); return nil; }
+	if(usbcmd(d, 0xA1, GET_RES, p->cs << 8, term, res, p->len) < p->len){ errorcode(d, term); return nil; }
+	if(usbcmd(d, 0xA1, GET_MAX, p->cs << 8, term, max, p->len) < p->len){ errorcode(d, term); return nil; }
+	switch(p->len){
+	case 1: return smprint("%ud %ud/%ud/%ud", (uchar)cur[0], (uchar)min[0], (uchar)res[0], (uchar)max[0]);
+	case 2: return smprint("%ud %ud/%ud/%ud", (ushort)GET2(cur), (ushort)GET2(min), (ushort)GET2(res), (ushort)GET2(max));
+	case 4: return smprint("%ud %ud/%ud/%ud", (uint)GET4(cur), (uint)GET4(min), (uint)GET4(res), (uint)GET4(max));
+	}
+	werrstr("pintread: unimplemented length %d", p->len);
+	return nil;
+}
+
+int
+puintwrite(Cam *c, int term, Param *p, char **f, int nf)
+{
+	uint v;
+	char *sp;
+	uchar buf[4];
+	
+	if(nf != 1) return -1;
+	v = strtoul(f[0], &sp, 0);
+	if(*f[0] == 0 || *sp != 0) return -1;
+	buf[0] = v;
+	buf[1] = v >> 8;
+	buf[2] = v >> 16;
+	buf[3] = v >> 24;
+	if(usbcmd(c->dev, 0x21, SET_CUR, p->cs << 8, term, buf, p->len) < p->len){ errorcode(c->dev, term); return -1; }
+	return 0;
+}
+
+char *
+penumread(Cam *c, int term, Param *p)
+{
+	uchar cur[4];
+	uint val;
+
+	if(infocheck(c, term, p) < 0) return nil;
+	if(usbcmd(c->dev, 0xA1, GET_CUR, p->cs << 8, term, cur, p->len) < p->len){ errorcode(c->dev, term); return nil; }
+	switch(p->len){
+	case 1: val = cur[0]; break;
+	case 2: val = GET2(cur); break;
+	case 4: val = GET4(cur); break;
+	default:
+		werrstr("pintread: unimplemented length %d", p->len);
+		return nil;
+	}
+	if(val >= p->auxi || ((char**)p->auxp)[val] == nil)
+		return smprint("%d", val);
+	return smprint("%s", ((char**)p->auxp)[val]);
+}
+
+int
+penumwrite(Cam *c, int term, Param *p, char **f, int nf)
+{
+	uint i;
+	uchar buf[4];
+
+	if(nf != 1) return -1;
+	for(i = 0; i < p->auxi; i++)
+		if(cistrcmp(((char**)p->auxp)[i], f[0]) == 0)
+			break;
+	if(i == p->auxi)
+		return -1;
+	buf[0] = i;
+	buf[1] = i >> 8;
+	buf[2] = i >> 16;
+	buf[3] = i >> 24;
+	if(usbcmd(c->dev, 0x21, SET_CUR, p->cs << 8, term, buf, p->len) < p->len){ errorcode(c->dev, term); return -1; }
+	return 0;
+}
+
+char *
+pformatread(Cam *c, int, Param *)
+{
+	Format *f;
+	VSUncompressedFrame *g;
+	char buf[5];
+	
+	if(c->pc.bFormatIndex >= c->nformat) goto nope;
+	f = c->format[c->pc.bFormatIndex];
+	if(f == nil) goto nope;
+	if(c->pc.bFrameIndex >= f->nframe) goto nope;
+	g = f->frame[c->pc.bFrameIndex];
+	if(g == nil) goto nope;
+	memcpy(buf, f->desc->guidFormat, 4);
+	buf[4] = 0;
+	return smprint("%dx%dx%d-%s", GET2(g->wWidth), GET2(g->wHeight), f->desc->bBitsPerPixel, buf);
+nope:
+	return smprint("#%d,%d", c->pc.bFormatIndex, c->pc.bFrameIndex);
+}
+
+void
+frameinterval(Cam *c, VSUncompressedFrame *f, double t)
+{
+	double δ, minδ;
+	int i, mini;
+	uint min, max, step, val;
+
+	if(f->bFrameIntervalType == 0){
+		min = GET4(f->dwFrameInterval[0]);
+		max = GET4(f->dwFrameInterval[1]);
+		step = GET4(f->dwFrameInterval[2]);
+		if(t <= min)
+			val = min;
+		else if(t >= max)
+			val = max;
+		else{
+			val = floor((t - min) / step) * step + min;
+			if(t >= val + step / 2.0)
+				t += step;
+		}
+	}else{
+		mini = -1;
+		for(i = 0; i < f->bFrameIntervalType; i++){
+			δ = fabs(((u32int)GET4(f->dwFrameInterval[i])) - t);
+			if(mini < 0 || δ < minδ){
+				mini = i;
+				minδ = δ;
+			}
+		}
+		assert(mini >= 0);
+		val = GET4(f->dwFrameInterval[mini]);
+	}
+	PUT4(c->pc.dwFrameInterval, val);
+}
+
+int
+findres(Cam *c, int w, int h, int fr)
+{
+	Format *f;
+	VSUncompressedFrame *g;
+	int i;
+
+	if(fr >= c->nformat || (f = c->format[fr]) == nil) return -1;
+	for(i = 0; i < f->nframe; i++){
+		g = f->frame[i];
+		if(g == nil) continue;
+		if(GET2(g->wWidth) == w && GET2(g->wHeight) == h)
+			return i;
+	}
+	return -1;
+}
+
+int
+pformatwrite(Cam *c, int, Param *, char **args, int nargs)
+{
+	int w, h, bpp;
+	char *p;
+	int i;
+	int j;
+	char *q;
+	Format *f;
+
+	if(nargs != 1) return -1;
+	p = args[0];
+	if(*p == 0) return -1;
+	w = strtol(p, &p, 0);
+	if(*p != 'x') return -1;
+	h = strtol(p + 1, &q, 0);
+	if(q == p + 1) return -1;
+	p = q;
+	if(*p == 0){
+		j = c->pc.bFormatIndex;
+		i = findres(c, w, h, j);
+		if(i < 0)
+			for(j = 0; j < c->nformat; j++){
+				i = findres(c, w, h, j);
+				if(i >= 0) break;
+			}
+	}else{
+		if(*p != 'x' || *++p == '-') return -1;
+		bpp = strtol(p, &p, 0);
+		if(*p != '-') return -1;
+		if(strlen(p) != 4) return -1;
+		i = -1;
+		for(j = 0; j < c->nformat; j++){
+			if((f = c->format[j]) == nil) continue;
+			if(f->desc->bBitsPerPixel != bpp) continue;
+			if(memcmp(f->desc->guidFormat, p, 4) != 0) continue;
+			i = findres(c, w, h, j);
+			if(i >= 0) break;
+		}
+	}
+	if(i < 0)
+		return -1;
+	if(c->active != 0){
+		werrstr("camera active");
+		return -1;
+	}
+	c->pc.bFormatIndex = j;
+	c->pc.bFrameIndex = i;
+	frameinterval(c, c->format[j]->frame[i], GET4(c->pc.dwFrameInterval));
+	return 0;
+}
+
+char *
+pfpsread(Cam *c, int, Param *)
+{
+	if(GET4(c->pc.dwFrameInterval) == 0)
+		return smprint("?");
+	return smprint("%.2f", 10e6 / GET4(c->pc.dwFrameInterval));
+}
+
+int
+pfpswrite(Cam *c, int, Param *, char **args, int nargs)
+{
+	double d, t;
+	char *sp;
+	VSUncompressedFrame *f;
+	
+	if(nargs != 1) return -1;
+	d = strtod(args[0], &sp);
+	if(*args[0] == 0 || *sp != 0) return -1;
+	if(getframedesc(c, c->pc.bFormatIndex, c->pc.bFrameIndex, nil, &f) < 0){
+		werrstr("invalid format active");
+		return -1;
+	}
+	if(isNaN(d) || isInf(d, 1) || d <= 0) return -1;
+	if(c->active != 0){
+		werrstr("camera active");
+		return -1;
+	}
+	t = 10e6 / d;
+	frameinterval(c, f, t);
+	return 0;
+}
+
+//static char *autoexposure[] = {"manual", "auto", "shutter", "aperture"};
+static char *powerlinefrequency[] = {"disabled", "50", "60", "auto"};
+
+static Param params[] = {
+	{"format", PARAMSPEC, -1, -1, -1, pformatread, pformatwrite},
+	{"fps", PARAMSPEC, -1, -1, -1, pfpsread, pfpswrite},
+	{"progressive", PARAMCT, CT_SCANNING_MODE_CONTROL, 1, 0, pboolread, pboolwrite},
+//	{"auto-exposure-mode", PARAMCT, CT_AE_MODE_CONTROL, 1, 1, pbitread, pbitwrite, autoexposure, nelem(autoexposure)},
+	{"auto-exposure-priority", PARAMCT, CT_AE_PRIORITY_CONTROL, 1, 2, pboolread, pboolwrite},
+	{"exposure-time", PARAMCT, CT_EXPOSURE_TIME_ABSOLUTE_CONTROL, 4, 3, puintread, puintwrite},
+	{"focus", PARAMCT, CT_FOCUS_ABSOLUTE_CONTROL, 2, 5, puintread, puintwrite},
+	{"focus-simple", PARAMCT, CT_FOCUS_SIMPLE_CONTROL, 1, 19, puintread, puintwrite},
+	{"focus-auto", PARAMCT, CT_FOCUS_AUTO_CONTROL, 1, 17, pboolread, pboolwrite},
+	{"iris", PARAMCT, CT_IRIS_ABSOLUTE_CONTROL, 2, 7, puintread, puintwrite},
+	{"zoom", PARAMCT, CT_ZOOM_ABSOLUTE_CONTROL, 2, 9, puintread, puintwrite},
+	{"backlight-compensation", PARAMPU, PU_BACKLIGHT_COMPENSATION_CONTROL, 2, 8, puintread, puintwrite},
+	{"brightness", PARAMPU, PU_BRIGHTNESS_CONTROL, 2, 0, pintread, pintwrite},
+	{"contrast", PARAMPU, PU_CONTRAST_CONTROL, 2, 1, puintread, puintwrite},
+	{"contrast-auto", PARAMPU, PU_CONTRAST_AUTO_CONTROL, 1, 18, pboolread, pboolwrite},
+	{"gain", PARAMPU, PU_GAIN_CONTROL, 2, 9, puintread, puintwrite},
+	{"powerline-frequency", PARAMPU, PU_POWER_LINE_FREQUENCY_CONTROL, 1, 10, penumread, penumwrite, powerlinefrequency, nelem(powerlinefrequency)},
+	{"hue", PARAMPU, PU_HUE_CONTROL, 2, 2, pintread, pintwrite},
+	{"hue-auto", PARAMPU, PU_HUE_AUTO_CONTROL, 1, 11, pboolread, pboolwrite},
+	{"saturation", PARAMPU, PU_SATURATION_CONTROL, 2, 3, puintread, puintwrite},
+	{"sharpness", PARAMPU, PU_SHARPNESS_CONTROL, 2, 4, puintread, puintwrite},
+	{"gamma", PARAMPU, PU_GAMMA_CONTROL, 2, 5, puintread, puintwrite},
+	{"white-balance-temperature", PARAMPU, PU_WHITE_BALANCE_TEMPERATURE_CONTROL, 2, 6, puintread, puintwrite},
+	{"white-balance-temperature-auto", PARAMPU, PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL, 1, 12, pboolread, pboolwrite},
+};
+
+int
+unittype(int i, uchar **ctlp)
+{
+	if(unit[i] == nil)
+		return -1;
+	switch(unit[i]->bDescriptorSubtype){
+	case VC_INPUT_TERMINAL:
+		if(GET2(((VCInputTerminal*)unit[i])->wTerminalType) == ITT_CAMERA){
+			if(ctlp != nil) *ctlp = ((VCCameraTerminal*)unit[i])->bmControls;
+			return PARAMCT;
+		}
+		break;
+	case VC_PROCESSING_UNIT:
+		if(ctlp != nil) *ctlp = ((VCProcessingUnit*)unit[i])->bmControls;
+		return PARAMPU;
+	}
+	return -1;
+}
+
+char *
+ctlread(Cam *c)
+{
+	Fmt f;
+	int i;
+	int ut;
+	Param *p;
+	uchar *bmControls;
+	int ifid;
+	char *str;
+	
+	fmtstrinit(&f);
+	for(p = params; p < params + nelem(params); p++){
+		if(p->type != PARAMSPEC) continue;
+		str = p->read(c, c->iface->id, p);
+		if(str == nil)
+			continue;
+		fmtprint(&f, "0 %s %s\n", p->name, str);
+		free(str);
+	}
+	for(i = 0; i < nunit; i++){
+		ut = unittype(i, &bmControls);
+		if(ut < 0) continue;
+		ifid = unitif[i]->id;
+		for(p = params; p < params + nelem(params); p++){
+			if(p->type != ut) continue;
+			if(bmControls != nil && p->flag >= 0 && (bmControls[p->flag >> 3] & 1<<(p->flag & 7)) == 0)
+				continue;
+			str = p->read(c, i << 8 | ifid, p);
+			if(str == nil)
+				continue;
+			fmtprint(&f, "%d %s %s\n", i, p->name, str);
+			free(str);
+		}
+	}
+	return fmtstrflush(&f);
+}
+
+static Param *
+findparam(char *s)
+{
+	Param *p;
+	
+	for(p = params; p < params + nelem(params); p++)
+		if(strcmp(s, p->name) == 0)
+			return p;
+	werrstr("no such parameter");
+	return nil;
+}
+
+static int
+unitbytype(int type)
+{
+	int i;
+
+	for(i = 0; i < nunit; i++)
+		if(unittype(i, nil) == type)
+			return i;
+	werrstr("no matching unit");
+	return -1;
+}
+
+int
+ctlwrite(Cam *c, char *msg)
+{
+	char *f[10], *sp;
+	uchar *bmControls;
+	Param *p;
+	int aut;
+	int nf;
+	int uid, ifid;
+	
+	nf = tokenize(msg, f, nelem(f));
+	if(nf == nelem(f))
+		return -1;
+	uid = strtoul(f[0], &sp, 0);
+	aut = *f[0] == 0 || *sp != 0;
+	if(aut){
+		p = findparam(f[0]);
+		if(p == nil)
+			return -1;
+		if(p->type == PARAMSPEC)
+			uid = 0;
+		else
+			uid = unitbytype(p->type);
+		if(uid < 0)
+			return -1;
+	}else{
+		p = findparam(f[1]);
+		if(p == nil)
+			return -1;
+		if((uint)uid >= nunit || unit[uid] == nil){
+			werrstr("no such unit");
+			return -1;
+		}
+	}
+	if(p->type != PARAMSPEC){
+		if(unittype(uid, &bmControls) != p->type){
+			werrstr("unit does not have this parameter");
+			return -1;
+		}
+		if(bmControls != nil && p->flag >= 0 && (bmControls[p->flag >> 3] & 1<<(p->flag & 7)) == 0){
+			werrstr("parameter not available");
+			return -1;
+		}
+		ifid = unitif[uid]->id;
+	}else
+		ifid = c->iface->id;
+	if(p->write == nil){
+		werrstr("read-only parameter");
+		return -1;
+	}
+	return p->write(c, uid << 8 | ifid, p, f + (2 - aut), nf - (2 - aut));
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/cam/dat.h
@@ -1,0 +1,38 @@
+typedef struct VFrame VFrame;
+typedef struct Cam Cam;
+typedef struct Format Format;
+
+struct Format {
+	VSUncompressedFormat *desc;
+	int nframe;
+	VSUncompressedFrame **frame;
+};
+
+struct VFrame {
+	int n, sz, p;
+	uchar *d;
+	VFrame *next;
+};
+
+struct Cam {
+	Dev *dev, *ep;
+	Iface *iface;
+	VSInputHeader *hdr;
+	int nformat;
+	Format **format;
+	ProbeControl pc;
+	Cam *next;
+	File *ctlfile, *formatsfile, *videofile, *descfile, *framefile;
+	
+	int active, abort;
+	VFrame *actl;
+	VFrame *freel;
+	Req *delreq;
+	QLock qulock;
+	int cvtid;
+	int framemode;
+};
+
+extern int nunit;
+extern VCUnit **unit;
+extern Iface **unitif;
--- /dev/null
+++ b/sys/src/cmd/nusb/cam/descprint.c
@@ -1,0 +1,360 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "usb.h"
+#include "uvc.h"
+
+void
+printVCHeader(Fmt *fmt, void *vp)
+{
+	VCHeader *p;
+	int i;
+
+	p = vp;
+	fmtprint(fmt, "VCHeader:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbNumFormats = %d\n", p->bNumFormats);
+	fmtprint(fmt, "\twTotalLength = %d\n", GET2(p->wTotalLength));
+	fmtprint(fmt, "\tdwClockFrequency = %d\n", GET2(p->dwClockFrequency));
+	fmtprint(fmt, "\tbInCollection = %d\n", p->bInCollection);
+	for(i = 0; i < p->bInCollection; i++)
+		fmtprint(fmt, "\tbaInterfaceNr(%d) = %d\n", i+1, p->baInterfaceNr[i]);
+}
+
+void
+printVCInputTerminal(Fmt *fmt, void *vp)
+{
+	VCInputTerminal *p;
+
+	p = vp;
+	fmtprint(fmt, "VCInputTerminal:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbTerminalID = %d\n", p->bTerminalID);
+	fmtprint(fmt, "\twTerminalType = %#.4x\n", GET2(p->wTerminalType));
+	fmtprint(fmt, "\tbAssocTerminal = %d\n", p->bAssocTerminal);
+	fmtprint(fmt, "\tiTerminal = %d\n", p->iTerminal);
+}
+
+void
+printVCOutputTerminal(Fmt *fmt, void *vp)
+{
+	VCOutputTerminal *p;
+
+	p = vp;
+	fmtprint(fmt, "VCOutputTerminal:\n");
+	fmtprint(fmt, "\tbLength = %#.2x\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbTerminalID = %#.2x\n", p->bTerminalID);
+	fmtprint(fmt, "\twTerminalType = %#.4x\n", GET2(p->wTerminalType));
+	fmtprint(fmt, "\tbAssocTerminal = %#.2x\n", p->bAssocTerminal);
+	fmtprint(fmt, "\tbSourceID = %#.2x\n", p->bSourceID);
+	fmtprint(fmt, "\tiTerminal = %#.2x\n", p->iTerminal);
+}
+
+void
+printVCCameraTerminal(Fmt *fmt, void *vp)
+{
+	VCCameraTerminal *p;
+
+	p = vp;
+	fmtprint(fmt, "VCCameraTerminal:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbTerminalID = %d\n", p->bTerminalID);
+	fmtprint(fmt, "\twTerminalType = %#.4x\n", GET2(p->wTerminalType));
+	fmtprint(fmt, "\tbAssocTerminal = %d\n", p->bAssocTerminal);
+	fmtprint(fmt, "\tiTerminal = %d\n", p->iTerminal);
+	fmtprint(fmt, "\twObjectiveFocalLengthMin = %#.4x\n", GET2(p->wObjectiveFocalLengthMin));
+	fmtprint(fmt, "\twObjectiveFocalLengthMax = %#.4x\n", GET2(p->wObjectiveFocalLengthMax));
+	fmtprint(fmt, "\twOcularFocalLength = %#.4x\n", GET2(p->wOcularFocalLength));
+	fmtprint(fmt, "\tbControlSize = %d\n", p->bControlSize);
+	fmtprint(fmt, "\tbmControls = %#.6x\n", GET3(p->bmControls));
+}
+
+void
+printVCSelectorUnit(Fmt *fmt, void *vp)
+{
+	VCSelectorUnit *p;
+	int i;
+
+	p = vp;
+	fmtprint(fmt, "VCSelectorUnit:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbUnitID = %d\n", p->bUnitID);
+	fmtprint(fmt, "\tbNrInPins = %d\n", p->bNrInPins);
+	for(i = 0; i < p->bNrInPins; i++)
+		fmtprint(fmt, "\tbaSourceID(%d) = %d\n", i+1, p->baSourceID[i]);
+	fmtprint(fmt, "\tiSelector = %d\n", p->baSourceID[i]);
+}
+
+void
+printVCProcessingUnit(Fmt *fmt, void *vp)
+{
+	VCProcessingUnit *p;
+
+	p = vp;
+	fmtprint(fmt, "VCProcessingUnit:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbUnitID = %d\n", p->bUnitID);
+	fmtprint(fmt, "\tbSourceID = %d\n", p->bSourceID);
+	fmtprint(fmt, "\twMaxMultiplier = %d\n", GET2(p->wMaxMultiplier));
+	fmtprint(fmt, "\tbControlSize = %d\n", p->bControlSize);
+	fmtprint(fmt, "\tbmControls = %#.6x\n", GET3(p->bmControls));
+	fmtprint(fmt, "\tiProcessing = %d\n", p->iProcessing);
+	fmtprint(fmt, "\tbmVideoStandards = %#.2x\n", p->bmVideoStandards);
+}
+
+void
+printVCEncodingUnit(Fmt *fmt, void *vp)
+{
+	VCEncodingUnit *p;
+
+	p = vp;
+	fmtprint(fmt, "VCEncodingUnit:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbUnitID = %d\n", p->bUnitID);
+	fmtprint(fmt, "\tbSourceID = %d\n", p->bSourceID);
+	fmtprint(fmt, "\tiEncoding = %d\n", p->iEncoding);
+	fmtprint(fmt, "\tbControlSize = %d\n", p->bControlSize);
+	fmtprint(fmt, "\tbmControls = %#.6x\n", GET3(p->bmControls));
+	fmtprint(fmt, "\tbmControlsRuntime = %#.6x\n", GET3(p->bmControlsRuntime));
+}
+
+void
+printVCExtensionUnit(Fmt *fmt, void *vp)
+{
+	VCExtensionUnit *p;
+	int i, i0, e;
+
+	p = vp;
+	fmtprint(fmt, "VCExtensionUnit:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbUnitID = %d\n", p->bUnitID);
+	fmtprint(fmt, "\tbNumControls = %d\n", p->bNumControls);
+	fmtprint(fmt, "\tbNrInPins = %d\n", p->bNrInPins);
+	for(i = 0; i < p->bNrInPins; i++)
+		fmtprint(fmt, "\tbaSourceID(%d) = %d\n", i+1, p->baSourceID[i]);
+	fmtprint(fmt, "\tbControlSize = %d\n", p->baSourceID[i]);
+	i0 = i;
+	e = i + p->baSourceID[i] + 1;
+	for(; i < e; i++)
+		fmtprint(fmt, "\tbmControls(%d) = %d\n", i - i0 + 1, p->baSourceID[i]);
+	fmtprint(fmt, "\tiExtension = %d\n", p->baSourceID[i]);
+}
+
+void
+printVSInputHeader(Fmt *fmt, void *vp)
+{
+	VSInputHeader *p;
+	int i;
+
+	p = vp;
+	fmtprint(fmt, "VSInputHeader:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbNumFormats = %d\n", p->bNumFormats);
+	fmtprint(fmt, "\twTotalLength = %d\n", GET2(p->wTotalLength));
+	fmtprint(fmt, "\tbEndpointAddress = %#x\n", p->bEndpointAddress);
+	fmtprint(fmt, "\tbmInfo = %#.2x\n", p->bmInfo);
+	fmtprint(fmt, "\tbTerminalLink = %d\n", p->bTerminalLink);
+	fmtprint(fmt, "\tbStillCaptureMethod = %d\n", p->bStillCaptureMethod);
+	fmtprint(fmt, "\tbTriggerSupport = %#.2x\n", p->bTriggerSupport);
+	fmtprint(fmt, "\tbTriggerUsage = %#.2x\n", p->bTriggerUsage);
+	fmtprint(fmt, "\tbControlSize = %d\n", p->bControlSize);
+	for(i = 0; i < p->bControlSize; i++)
+		fmtprint(fmt, "\tbmaControls(%d) = %d\n", i+1, p->bmaControls[i]);
+}
+
+void
+printVSOutputHeader(Fmt *fmt, void *vp)
+{
+	VSOutputHeader *p;
+	int i;
+
+	p = vp;
+	fmtprint(fmt, "VSOutputHeader:\n");
+	fmtprint(fmt, "\tbLength = %#.2x\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbNumFormats = %#.2x\n", p->bNumFormats);
+	fmtprint(fmt, "\twTotalLength = %#.4x\n", GET2(p->wTotalLength));
+	fmtprint(fmt, "\tbEndpointAddress = %#.2x\n", p->bEndpointAddress);
+	fmtprint(fmt, "\tbTerminalLink = %#.2x\n", p->bTerminalLink);
+	fmtprint(fmt, "\tbControlSize = %#.2x\n", p->bControlSize);
+	for(i = 0; i < p->bControlSize; i++)
+		fmtprint(fmt, "\tbmaControls(%d) = %d\n", i+1, p->bmaControls[i]);
+}
+
+void
+printVSStillFrame(Fmt *fmt, void *vp)
+{
+	VSStillFrame *p;
+	int i, j;
+
+	p = vp;
+	fmtprint(fmt, "VSStillFrame:\n");
+	fmtprint(fmt, "\tbLength = %#.2x\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbEndpointAddress = %#.2x\n", p->bEndpointAddress);
+	fmtprint(fmt, "\tbNumImageSizePatterns = %#.2x\n", p->bNumImageSizePatterns);
+	for(i = 0; i < p->bNumImageSizePatterns; i++){
+		fmtprint(fmt, "\twWidth(%d) = %d\n", i+1, GET2(&p->data[4 * i]));
+		fmtprint(fmt, "\twHeight(%d) = %d\n", i+1, GET2(&p->data[4 * i + 2]));
+	}
+	fmtprint(fmt, "\tbNumCompressionPatterns = %#.2x\n", p->data[4 * i]);
+	for(j = 0; j < p->data[4 * i]; i++)
+		fmtprint(fmt, "\tbCompression(%d) = %#.2x", j + 1,GET2(&p->data[4 * i + 1 + j]));
+}
+
+void
+printVSUncompressedFormat(Fmt *fmt, void *vp)
+{
+	VSUncompressedFormat *p;
+
+	p = vp;
+	fmtprint(fmt, "VSUncompressedFormat:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbFormatIndex = %d\n", p->bFormatIndex);
+	fmtprint(fmt, "\tbNumFrameDescriptors = %d\n", p->bNumFrameDescriptors);
+	fmtprint(fmt, "\tbBitsPerPixel = %d\n", p->bBitsPerPixel);
+	fmtprint(fmt, "\tbDefaultFrameIndex = %d\n", p->bDefaultFrameIndex);
+	fmtprint(fmt, "\tbAspectRatioX = %d\n", p->bAspectRatioX);
+	fmtprint(fmt, "\tbAspectRatioY = %d\n", p->bAspectRatioY);
+	fmtprint(fmt, "\tbmInterlaceFlags = %#.2x\n", p->bmInterlaceFlags);
+	fmtprint(fmt, "\tbCopyProtect = %#.2x\n", p->bCopyProtect);
+}
+
+void
+printVSUncompressedFrame(Fmt *fmt, void *vp)
+{
+	VSUncompressedFrame *p;
+	int i;
+
+	p = vp;
+	fmtprint(fmt, "VSUncompressedFrame:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbFrameIndex = %d\n", p->bFrameIndex);
+	fmtprint(fmt, "\tbmCapabilities = %#.2x\n", p->bmCapabilities);
+	fmtprint(fmt, "\twWidth = %d\n", GET2(p->wWidth));
+	fmtprint(fmt, "\twHeight = %d\n", GET2(p->wHeight));
+	fmtprint(fmt, "\tdwMinBitRate = %d\n", GET4(p->dwMinBitRate));
+	fmtprint(fmt, "\tdwMaxBitRate = %d\n", GET4(p->dwMaxBitRate));
+	fmtprint(fmt, "\tdwMaxVideoFrameBufferSize = %d\n", GET4(p->dwMaxVideoFrameBufferSize));
+	fmtprint(fmt, "\tdwDefaultFrameInterval = %d\n", GET4(p->dwDefaultFrameInterval));
+	fmtprint(fmt, "\tbFrameIntervalType = %d\n", p->bFrameIntervalType);
+	if(p->bFrameIntervalType == 0){
+		fmtprint(fmt, "\tdwMinFrameInterval = %d\n", GET4(p->dwFrameInterval[0]));
+		fmtprint(fmt, "\tdwMaxFrameInterval = %d\n", GET4(p->dwFrameInterval[1]));
+		fmtprint(fmt, "\tdwFrameIntervalStep = %d\n", GET4(p->dwFrameInterval[2]));
+	}
+	for(i = 0; i < p->bFrameIntervalType; i++)
+		fmtprint(fmt, "\tdwFrameInterval = %d\n", GET4(p->dwFrameInterval[i]));
+}
+
+void
+printVSColorFormat(Fmt *fmt, void *vp)
+{
+	VSColorFormat *p;
+
+	p = vp;
+	fmtprint(fmt, "VSColorFormat:\n");
+	fmtprint(fmt, "\tbLength = %d\n", p->bLength);
+	fmtprint(fmt, "\tbDescriptorType = %#.2x\n", p->bDescriptorType);
+	fmtprint(fmt, "\tbDescriptorSubtype = %#.2x\n", p->bDescriptorSubtype);
+	fmtprint(fmt, "\tbColorPrimaries = %d\n", p->bColorPrimaries);
+	fmtprint(fmt, "\tbTransferCharacteristics = %d\n", p->bTransferCharacteristics);
+	fmtprint(fmt, "\tbMatrixCoefficients = %d\n", p->bMatrixCoefficients);
+}
+
+void
+printProbeControl(Fmt *fmt, void *vp)
+{
+	ProbeControl *p;
+	int i;
+
+	p = vp;
+	fmtprint(fmt, "ProbeControl:\n");
+	fmtprint(fmt, "\tbmHint = %#.4ux\n", GET2(p->bmHint));
+	fmtprint(fmt, "\tbFormatIndex = %#.2ux\n", p->bFormatIndex);
+	fmtprint(fmt, "\tbFrameIndex = %#.2ux\n", p->bFrameIndex);
+	fmtprint(fmt, "\tdwFrameInterval = %#.8ux\n", GET4(p->dwFrameInterval));
+	fmtprint(fmt, "\twKeyFrameRate = %#.4ux\n", GET2(p->wKeyFrameRate));
+	fmtprint(fmt, "\twPFrameRate = %#.4ux\n", GET2(p->wPFrameRate));
+	fmtprint(fmt, "\twCompQuality = %#.4ux\n", GET2(p->wCompQuality));
+	fmtprint(fmt, "\twCompWindowSize = %#.4ux\n", GET2(p->wCompWindowSize));
+	fmtprint(fmt, "\twDelay = %#.4ux\n", GET2(p->wDelay));
+	fmtprint(fmt, "\tdwMaxVideoFrameSize = %#.8ux\n", GET4(p->dwMaxVideoFrameSize));
+	fmtprint(fmt, "\tdwMaxPayloadTransferSize = %#.8ux\n", GET4(p->dwMaxPayloadTransferSize));
+	fmtprint(fmt, "\tdwClockFrequency = %#.8ux\n", GET4(p->dwClockFrequency));
+	fmtprint(fmt, "\tbmFramingInfo = %#.2ux\n", p->bmFramingInfo);
+	fmtprint(fmt, "\tbPreferedVersion = %#.2ux\n", p->bPreferedVersion);
+	fmtprint(fmt, "\tbMinVersion = %#.2ux\n", p->bMinVersion);
+	fmtprint(fmt, "\tbMaxVersion = %#.2ux\n", p->bMaxVersion);
+	fmtprint(fmt, "\tbBitDepthLuma = %#.2ux\n", p->bBitDepthLuma);
+	fmtprint(fmt, "\tbmSettings = %#.2ux\n", p->bmSettings);
+	fmtprint(fmt, "\tbMaxNumberOfRefFramesPlus1 = %#.2ux\n", p->bMaxNumberOfRefFramesPlus1);
+	fmtprint(fmt, "\tbmRateControlModes = %#.4ux\n", GET2(p->bmRateControlModes));
+	for(i = 0; i < 4; i++)
+		fmtprint(fmt, "\tbmLayoutPerStream(%d) = %#.4ux\n", i + 1, GET2(&p->bmLayoutPerStream[2 * i]));
+}
+
+void
+printDescriptor(Fmt *fmt, Iface *iface, void *vp)
+{
+	VCDescriptor *vdp;
+	
+	vdp = vp;
+	switch(Subclass(iface->csp)){
+	case SC_VIDEOCONTROL:
+		switch(vdp->bDescriptorSubtype){
+		case VC_HEADER: printVCHeader(fmt, vdp); break;
+		case VC_INPUT_TERMINAL:
+			if(GET2(((VCInputTerminal*)vdp)->wTerminalType) == ITT_CAMERA)
+				printVCCameraTerminal(fmt, vdp);
+			else
+				printVCInputTerminal(fmt, vdp);
+			break;
+		case VC_OUTPUT_TERMINAL: printVCOutputTerminal(fmt, vdp); break;
+		case VC_SELECTOR_UNIT: printVCSelectorUnit(fmt, vdp); break;
+		case VC_PROCESSING_UNIT: printVCProcessingUnit(fmt, vdp); break;
+		case VC_ENCODING_UNIT: printVCEncodingUnit(fmt, vdp); break;
+		case VC_EXTENSION_UNIT: printVCExtensionUnit(fmt, vdp); break;
+		default: fmtprint(fmt, "unknown video control descriptor type %#.2x\n", vdp->bDescriptorSubtype);
+		}
+		break;
+	case SC_VIDEOSTREAMING:
+		switch(vdp->bDescriptorSubtype){
+		case VS_INPUT_HEADER: printVSInputHeader(fmt, vdp); break;
+		case VS_OUTPUT_HEADER: printVSOutputHeader(fmt, vdp); break;
+		case VS_STILL_IMAGE_FRAME: printVSStillFrame(fmt, vdp); break;
+		case VS_FORMAT_UNCOMPRESSED: printVSUncompressedFormat(fmt, vdp); break;
+		case VS_FRAME_UNCOMPRESSED: printVSUncompressedFrame(fmt, vdp); break;
+		case VS_COLORFORMAT: printVSColorFormat(fmt, vdp); break;
+		default: fmtprint(fmt, "unknown video streaming descriptor type %#.2x\n", vdp->bDescriptorSubtype);
+		}
+		break;
+	}
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/cam/fns.h
@@ -1,0 +1,8 @@
+char *ctlread(Cam *);
+void printDescriptor(Fmt *, Iface *, void *);
+int videoopen(Cam *, int);
+void videoclose(Cam *);
+void videoread(Req *, Cam *, int);
+void videoflush(Req *, Cam *);
+int getframedesc(Cam *, int, int, Format **, VSUncompressedFrame **);
+int ctlwrite(Cam *, char *);
--- /dev/null
+++ b/sys/src/cmd/nusb/cam/mkfile
@@ -1,0 +1,25 @@
+</$objtype/mkfile
+
+TARG=cam
+OFILES=\
+	cam.$O \
+	descprint.$O \
+	ctl.$O \
+	video.$O \
+
+HFILES=\
+	../lib/usb.h \
+	dat.h \
+	fns.h \
+
+LIB=../lib/usb.a$O
+
+BIN=/$objtype/bin/nusb
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+CFLAGS=-I../lib $CFLAGS
--- /dev/null
+++ b/sys/src/cmd/nusb/cam/uvc.h
@@ -1,0 +1,354 @@
+typedef struct VCDescriptor {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+} VCDescriptor;
+
+typedef struct VCHeader {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bNumFormats;
+	uchar wTotalLength[2];
+	uchar dwClockFrequency[4];
+	uchar bInCollection;
+	uchar baInterfaceNr[1];
+} VCHeader;
+
+typedef struct VCInputTerminal {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bTerminalID;
+	uchar wTerminalType[2];
+	uchar bAssocTerminal;
+	uchar iTerminal;
+} VCInputTerminal;
+
+typedef struct VCOutputTerminal {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bTerminalID;
+	uchar wTerminalType[2];
+	uchar bAssocTerminal;
+	uchar bSourceID;
+	uchar iTerminal;
+} VCOutputTerminal;
+
+typedef struct VCCameraTerminal {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bTerminalID;
+	uchar wTerminalType[2];
+	uchar bAssocTerminal;
+	uchar iTerminal;
+	uchar wObjectiveFocalLengthMin[2];
+	uchar wObjectiveFocalLengthMax[2];
+	uchar wOcularFocalLength[2];
+	uchar bControlSize;
+	uchar bmControls[3];
+} VCCameraTerminal;
+
+typedef struct VCUnit {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bUnitID;
+} VCUnit;
+
+typedef struct VCSelectorUnit {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bUnitID;
+	uchar bNrInPins;
+	uchar baSourceID[1];
+	/* after baSourceID: uchar iSelector; */
+} VCSelectorUnit;
+
+typedef struct VCProcessingUnit {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bUnitID;
+	uchar bSourceID;
+	uchar wMaxMultiplier[2];
+	uchar bControlSize;
+	uchar bmControls[3];
+	uchar iProcessing;
+	uchar bmVideoStandards;
+} VCProcessingUnit;
+
+typedef struct VCEncodingUnit {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bUnitID;
+	uchar bSourceID;
+	uchar iEncoding;
+	uchar bControlSize;
+	uchar bmControls[3];
+	uchar bmControlsRuntime[3];
+} VCEncodingUnit;
+
+typedef struct VCExtensionUnit {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bUnitID;
+	uchar guidExtensionCode[16];
+	uchar bNumControls;
+	uchar bNrInPins;
+	uchar baSourceID[1];
+	/*
+		uchar bControlSize;
+		uchar bmControls[1];
+		uchar iExtension;
+	*/
+} VCExtensionUnit;
+
+typedef struct VSInputHeader {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bNumFormats;
+	uchar wTotalLength[2];
+	uchar bEndpointAddress;
+	uchar bmInfo;
+	uchar bTerminalLink;
+	uchar bStillCaptureMethod;
+	uchar bTriggerSupport;
+	uchar bTriggerUsage;
+	uchar bControlSize;
+	uchar bmaControls[1];
+} VSInputHeader;
+
+typedef struct VSOutputHeader {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bNumFormats;
+	uchar wTotalLength[2];
+	uchar bEndpointAddress;
+	uchar bTerminalLink;
+	uchar bControlSize;
+	uchar bmaControls[1];
+} VSOutputHeader;
+
+typedef struct VSStillFrame {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bEndpointAddress;
+	uchar bNumImageSizePatterns;
+	uchar data[1];
+} VSStillFrame;
+
+typedef struct VSUncompressedFormat {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bFormatIndex;
+	uchar bNumFrameDescriptors;
+	uchar guidFormat[16];
+	uchar bBitsPerPixel;
+	uchar bDefaultFrameIndex;
+	uchar bAspectRatioX;
+	uchar bAspectRatioY;
+	uchar bmInterlaceFlags;
+	uchar bCopyProtect;
+} VSUncompressedFormat;
+
+typedef struct VSUncompressedFrame {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bFrameIndex;
+	uchar bmCapabilities;
+	uchar wWidth[2];
+	uchar wHeight[2];
+	uchar dwMinBitRate[4];
+	uchar dwMaxBitRate[4];
+	uchar dwMaxVideoFrameBufferSize[4];
+	uchar dwDefaultFrameInterval[4];
+	uchar bFrameIntervalType;
+	uchar dwFrameInterval[1][4];
+} VSUncompressedFrame;
+
+typedef struct VSColorFormat {
+	uchar bLength;
+	uchar bDescriptorType;
+	uchar bDescriptorSubtype;
+	uchar bColorPrimaries;
+	uchar bTransferCharacteristics;
+	uchar bMatrixCoefficients;
+} VSColorFormat;
+
+typedef struct ProbeControl {
+	uchar bmHint[2];
+	uchar bFormatIndex;
+	uchar bFrameIndex;
+	uchar dwFrameInterval[4];
+	uchar wKeyFrameRate[2];
+	uchar wPFrameRate[2];
+	uchar wCompQuality[2];
+	uchar wCompWindowSize[2];
+	uchar wDelay[2];
+	uchar dwMaxVideoFrameSize[4];
+	uchar dwMaxPayloadTransferSize[4];
+	uchar dwClockFrequency[4];
+	uchar bmFramingInfo;
+	uchar bPreferedVersion;
+	uchar bMinVersion;
+	uchar bMaxVersion;
+	uchar bBitDepthLuma;
+	uchar bmSettings;
+	uchar bMaxNumberOfRefFramesPlus1;
+	uchar bmRateControlModes[2];
+	uchar bmLayoutPerStream[8];
+} ProbeControl;
+
+enum {
+	CC_VIDEO = 0x0E,
+	SC_UNDEFINED = 0x00,
+	SC_VIDEOCONTROL = 0x01,
+	SC_VIDEOSTREAMING = 0x02,
+	SC_VIDEO_INTERFACE_COLLECTION = 0x03,
+	PC_PROTOCOL_UNDEFINED = 0x00,
+	PC_PROTOCOL_15 = 0x01,
+	CS_UNDEFINED = 0x20,
+	CS_DEVICE = 0x21,
+	CS_CONFIGURATION = 0x22,
+	CS_STRING = 0x23,
+	CS_INTERFACE = 0x24,
+	CS_ENDPOINT = 0x25,
+	VC_DESCRIPTOR_UNDEFINED = 0x00,
+	VC_HEADER = 0x01,
+	VC_INPUT_TERMINAL = 0x02,
+	VC_OUTPUT_TERMINAL = 0x03,
+	VC_SELECTOR_UNIT = 0x04,
+	VC_PROCESSING_UNIT = 0x05,
+	VC_EXTENSION_UNIT = 0x06,
+	VC_ENCODING_UNIT = 0x07,
+	VS_UNDEFINED = 0x00,
+	VS_INPUT_HEADER = 0x01,
+	VS_OUTPUT_HEADER = 0x02,
+	VS_STILL_IMAGE_FRAME = 0x03,
+	VS_FORMAT_UNCOMPRESSED = 0x04,
+	VS_FRAME_UNCOMPRESSED = 0x05,
+	VS_FORMAT_MJPEG = 0x06,
+	VS_FRAME_MJPEG = 0x07,
+	VS_FORMAT_MPEG2TS = 0x0A,
+	VS_FORMAT_DV = 0x0C,
+	VS_COLORFORMAT = 0x0D,
+	VS_FORMAT_FRAME_BASED = 0x10,
+	VS_FRAME_FRAME_BASED = 0x11,
+	VS_FORMAT_STREAM_BASED = 0x12,
+	VS_FORMAT_H264 = 0x13,
+	VS_FRAME_H264 = 0x14,
+	VS_FORMAT_H264_SIMULCAST = 0x15,
+	VS_FORMAT_VP8 = 0x16,
+	VS_FRAME_VP8 = 0x17,
+	VS_FORMAT_VP8_SIMULCAST = 0x18,
+	RC_UNDEFINED = 0x00,
+	SET_CUR = 0x01,
+	SET_CUR_ALL = 0x11,
+	GET_CUR = 0x81,
+	GET_MIN = 0x82,
+	GET_MAX = 0x83,
+	GET_RES = 0x84,
+	GET_LEN = 0x85,
+	GET_INFO = 0x86,
+	GET_DEF = 0x87,
+	GET_CUR_ALL = 0x91,
+	GET_MIN_ALL = 0x92,
+	GET_MAX_ALL = 0x93,
+	GET_RES_ALL = 0x94,
+	GET_DEF_ALL = 0x97,
+	VC_CONTROL_UNDEFINED = 0x00,
+	VC_VIDEO_POWER_MODE_CONTROL = 0x01,
+	VC_REQUEST_ERROR_CODE_CONTROL = 0x02,
+	TE_CONTROL_UNDEFINED = 0x00,
+	SU_CONTROL_UNDEFINED = 0x00,
+	SU_INPUT_SELECT_CONTROL = 0x01,
+	CT_CONTROL_UNDEFINED = 0x00,
+	CT_SCANNING_MODE_CONTROL = 0x01,
+	CT_AE_MODE_CONTROL = 0x02,
+	CT_AE_PRIORITY_CONTROL = 0x03,
+	CT_EXPOSURE_TIME_ABSOLUTE_CONTROL = 0x04,
+	CT_EXPOSURE_TIME_RELATIVE_CONTROL = 0x05,
+	CT_FOCUS_ABSOLUTE_CONTROL = 0x06,
+	CT_FOCUS_RELATIVE_CONTROL = 0x07,
+	CT_FOCUS_AUTO_CONTROL = 0x08,
+	CT_IRIS_ABSOLUTE_CONTROL = 0x09,
+	CT_IRIS_RELATIVE_CONTROL = 0x0a,
+	CT_ZOOM_ABSOLUTE_CONTROL = 0x0b,
+	CT_ZOOM_RELATIVE_CONTROL = 0x0c,
+	CT_PANTILT_ABSOLUTE_CONTROL = 0x0d,
+	CT_PANTILT_RELATIVE_CONTROL = 0x0e,
+	CT_ROLL_ABSOLUTE_CONTROL = 0x0f,
+	CT_ROLL_RELATIVE_CONTROL = 0x10,
+	CT_PRIVACY_CONTROL = 0x11,
+	CT_FOCUS_SIMPLE_CONTROL = 0x12,
+	CT_WINDOW_CONTROL = 0x13,
+	CT_REGION_OF_INTEREST_CONTROL = 0x14,
+	PU_CONTROL_UNDEFINED = 0x00,
+	PU_BACKLIGHT_COMPENSATION_CONTROL = 0x01,
+	PU_BRIGHTNESS_CONTROL = 0x02,
+	PU_CONTRAST_CONTROL = 0x03,
+	PU_GAIN_CONTROL = 0x04,
+	PU_POWER_LINE_FREQUENCY_CONTROL = 0x05,
+	PU_HUE_CONTROL = 0x06,
+	PU_SATURATION_CONTROL = 0x07,
+	PU_SHARPNESS_CONTROL = 0x08,
+	PU_GAMMA_CONTROL = 0x09,
+	PU_WHITE_BALANCE_TEMPERATURE_CONTROL = 0x0a,
+	PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL = 0x0b,
+	PU_WHITE_BALANCE_COMPONENT_CONTROL = 0x0c,
+	PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL = 0x0d,
+	PU_DIGITAL_MULTIPLIER_CONTROL = 0x0e,
+	PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL = 0x0f,
+	PU_HUE_AUTO_CONTROL = 0x10,
+	PU_ANALOG_VIDEO_STANDARD_CONTROL = 0x11,
+	PU_ANALOG_LOCK_STATUS_CONTROL = 0x12,
+	PU_CONTRAST_AUTO_CONTROL = 0x13,
+	EU_CONTROL_UNDEFINED = 0x00,
+	EU_SELECT_LAYER_CONTROL = 0x01,
+	EU_PROFILE_TOOLSET_CONTROL = 0x02,
+	EU_VIDEO_RESOLUTION_CONTROL = 0x03,
+	EU_MIN_FRAME_INTERVAL_CONTROL = 0x04,
+	EU_SLICE_MODE_CONTROL = 0x05,
+	EU_RATE_CONTROL_MODE_CONTROL = 0x06,
+	EU_AVERAGE_BITRATE_CONTROL = 0x07,
+	EU_CPB_SIZE_CONTROL = 0x08,
+	EU_PEAK_BIT_RATE_CONTROL = 0x09,
+	EU_QUANTIZATION_PARAMS_CONTROL = 0x0a,
+	EU_SYNC_REF_FRAME_CONTROL = 0x0b,
+	EU_LTR_BUFFER_ = 0x0c,
+	EU_LTR_PICTURE_CONTROL = 0x0d,
+	EU_LTR_VALIDATION_CONTROL = 0x0e,
+	EU_LEVEL_IDC_LIMIT_CONTROL = 0x0f,
+	EU_SEI_PAYLOADTYPE_CONTROL = 0x10,
+	EU_QP_RANGE_CONTROL = 0x11,
+	EU_PRIORITY_CONTROL = 0x12,
+	EU_START_OR_STOP_LAYER_CONTROL = 0x13,
+	EU_ERROR_RESILIENCY_CONTROL = 0x14,
+	XU_CONTROL_UNDEFINED = 0x00,
+	VS_CONTROL_UNDEFINED = 0x00,
+	VS_PROBE_CONTROL = 0x01,
+	VS_COMMIT_CONTROL = 0x02,
+	VS_STILL_PROBE_CONTROL = 0x03,
+	VS_STILL_COMMIT_CONTROL = 0x04,
+	VS_STILL_IMAGE_TRIGGER_CONTROL = 0x05,
+	VS_STREAM_ERROR_CODE_CONTROL = 0x06,
+	VS_GENERATE_KEY_FRAME_CONTROL = 0x07,
+	VS_UPDATE_FRAME_SEGMENT_CONTROL = 0x08,
+	VS_SYNCH_DELAY_CONTROL = 0x09,
+	ITT_VENDOR_SPECIFIC = 0x0200,
+	ITT_CAMERA = 0x0201,
+	ITT_MEDIA_TRANSPORT_INPUT,
+};
+
+#define	GET3(p)		(((p)[2] & 0xFF)<<16 | ((p)[1] & 0xFF)<<8 | ((p)[0] & 0xFF))
--- /dev/null
+++ b/sys/src/cmd/nusb/cam/video.c
@@ -1,0 +1,377 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <bio.h>
+#include "usb.h"
+#include "uvc.h"
+#include "dat.h"
+#include "fns.h"
+
+enum {
+	NFrames = 20, /* frames to buffer */
+};
+
+VFrame *
+grabframe(Cam *c)
+{
+	VFrame *v;
+	VFrame **l;
+
+	qlock(&c->qulock);
+	if(c->freel != nil)
+		l = &c->freel;
+	else{
+		assert(c->actl != nil);
+		if(c->actl->p == 0)
+			l = &c->actl;
+		else
+			l = &c->actl->next;
+	}
+	assert(*l != nil);
+	v = *l;
+	*l = v->next;
+	qunlock(&c->qulock);
+	v->next = nil;
+	v->n = 60;
+	v->p = 0;
+	return v;
+}
+
+void
+pushframe(Cam *c, VFrame *v)
+{
+	VFrame **l;
+	Req *r;
+	
+	v->next = nil;
+	qlock(&c->qulock);
+	for(l = &c->actl; *l != nil; l = &(*l)->next)
+		;
+	*l = v;
+	while(c->delreq != nil && c->actl != nil){
+		r = c->delreq;
+		c->delreq = (Req*)r->qu.next;
+		videoread(r, c, 0);
+	}
+	qunlock(&c->qulock);
+}
+
+void
+yuy2convert(Format *, VSUncompressedFrame *g, uchar *in, VFrame *out)
+{
+	int y, x, w, h;
+	double Y0, Y1, U, V, R, G, B;
+	uchar *ip, *op;
+	
+	w = GET2(g->wWidth);
+	h = GET2(g->wHeight);
+	op = out->d + out->n;
+	ip = in;
+	for(y = 0; y < h; y++)
+		for(x = 0; x < w; x += 2){
+			Y0 = ((int)ip[0] - 16) / 219.0;
+			Y1 = ((int)ip[2] - 16) / 219.0;
+			U = ((int)ip[1] - 128) / 224.0;
+			V = ((int)ip[3] - 128) / 224.0;
+			ip += 4;
+			R = Y0 + V;
+			B = Y0 + U;
+			G = Y0 - 0.509 * V - 0.194 * U;
+			if(R < 0) R = 0; if(R > 1) R = 1;
+			if(G < 0) G = 0; if(G > 1) G = 1;
+			if(B < 0) B = 0; if(B > 1) B = 1;
+			*op++ = R * 255;
+			*op++ = G * 255;
+			*op++ = B * 255;
+			R = Y1 + V;
+			B = Y1 + U;
+			G = Y1 - 0.509 * V - 0.194 * U;
+			if(R < 0) R = 0; if(R > 1) R = 1;
+			if(G < 0) G = 0; if(G > 1) G = 1;
+			if(B < 0) B = 0; if(B > 1) B = 1;
+			*op++ = R * 255;
+			*op++ = G * 255;
+			*op++ = B * 255;
+		}
+	out->n = op - out->d;
+}
+
+struct Converter {
+	uchar guid[16];
+	void (*fn)(Format *, VSUncompressedFrame *, uchar *, VFrame *);
+} converters[] = {
+	{{0x59,0x55,0x59,0x32, 0x00,0x00,0x10,0x00, 0x80,0x00,0x00,0xAA, 0x00,0x38,0x9B,0x71}, yuy2convert},
+};
+
+struct Converter *
+getconverter(Format *f)
+{
+	struct Converter *c;
+	uchar *guid;
+	
+	guid = f->desc->guidFormat;
+	for(c = converters; c < converters + nelem(converters); c++)
+		if(memcmp(guid, c->guid, 16) == 0)
+			return c;
+	werrstr("unknown format %.2X%.2X%.2X%.2X %.2X%.2X%.2X%.2X %.2X%.2X%.2X%.2X %.2X%.2X%.2X%.2X",
+		guid[0], guid[1], guid[2], guid[3],
+		guid[4], guid[5], guid[6], guid[7],
+		guid[8], guid[9], guid[10], guid[11],
+		guid[12], guid[13], guid[14], guid[15]);
+	return nil;
+}
+
+static void
+freeframes(VFrame **fp)
+{
+	VFrame *f, *g;
+	
+	for(f = *fp; f != nil; f = g){
+		g = f->next;
+		free(f);
+	}
+	*fp = nil;
+}
+
+void
+cvtproc(void *v)
+{
+	int frsz;
+	Cam *c;
+	Format *f;
+	VSUncompressedFrame *g;
+	uchar *fbuf;
+	int n;
+	int rc;
+	uchar buf[3*1024];
+	struct Converter *cvt;
+	int bufn;
+	int ob;
+	VFrame *of;
+	
+	c = v;
+	assert(getframedesc(c, c->pc.bFormatIndex, c->pc.bFrameIndex, &f, &g) >= 0);
+	cvt = getconverter(f);
+	assert(cvt != nil);
+	frsz = GET2(g->wWidth) * GET2(g->wHeight) * f->desc->bBitsPerPixel / 8;
+	fbuf = emallocz(frsz, 1);
+	bufn = 0;
+	ob = 0;
+	for(;;){
+		if(c->abort) break;
+		rc = read(c->ep->dfd, buf, sizeof(buf));
+		if(c->abort || rc < 0) break;
+		if(rc == 0) continue;
+		if(((ob ^ buf[1]) & 1) != 0 && bufn != 0){
+			if(!c->framemode || bufn == frsz){
+				if(bufn < frsz)
+					memset(fbuf + bufn, 0, frsz - bufn);
+				of = grabframe(c);
+				cvt->fn(f, g, fbuf, of);
+				pushframe(c, of);
+			}
+			bufn = 0;
+		}
+		ob = buf[1];
+		n = rc - buf[0];
+		if(n > frsz - bufn) n = frsz - bufn;
+		if(n > 0){
+			memcpy(fbuf + bufn, buf + buf[0], n);
+			bufn += n;
+		}
+			
+	}
+	qlock(&c->qulock);
+	freeframes(&c->actl);
+	freeframes(&c->freel);
+	c->abort = 1;
+	free(fbuf);
+	closedev(c->ep);
+	usbcmd(c->dev, 0x01, Rsetiface, 0, c->iface->id, nil, 0);
+	c->ep = nil;
+	c->active = 0;
+	c->abort = 0;
+	qunlock(&c->qulock);
+}
+
+static Altc *
+selaltc(Cam *c, ProbeControl *pc)
+{
+	int k;
+	uvlong bw, bw1, minbw;
+	int mink;
+	Format *fo;
+	VSUncompressedFrame *f;
+	Iface *iface;
+	Altc *altc;
+	
+	if(getframedesc(c, pc->bFormatIndex, pc->bFrameIndex, &fo, &f) < 0){
+		werrstr("selaltc: PROBE_CONTROL returned invalid bFormatIndex,bFrameIndex=%d,%d", pc->bFormatIndex, pc->bFrameIndex);
+		return nil;
+	}
+	bw = (uvlong)GET2(f->wWidth) * GET2(f->wHeight) * fo->desc->bBitsPerPixel * 10e6 / GET4(c->pc.dwFrameInterval);
+	iface = c->iface;
+	mink = -1;
+	for(k = 0; k < nelem(iface->altc); k++){
+		altc = iface->altc[k];
+		if(altc == nil) continue;
+		bw1 = altc->maxpkt * altc->ntds * 8 * 1000 * 8;
+		if(bw1 < bw) continue;
+		if(mink < 0 || bw1 < minbw){
+			minbw = bw1;
+			mink = k;
+		}
+	}
+	if(mink < 0){
+		werrstr("device does not have enough bandwidth (need %lld bit/s)", bw);
+		return nil;
+	}
+	if(usbcmd(c->dev, 0x01, Rsetiface, mink, iface->id, nil, 0) < 0){
+		werrstr("selaltc: SET_INTERFACE(%d, %d): %r", iface->id, mink);
+		return nil;
+	}
+	return iface->altc[mink];
+}
+
+static void
+mkframes(Cam *c)
+{
+	int i;
+	VSUncompressedFrame *f;
+	int frsz;
+	VFrame *v;
+
+	assert(getframedesc(c, c->pc.bFormatIndex, c->pc.bFrameIndex, nil, &f) >= 0);
+	frsz = GET2(f->wWidth) * GET2(f->wHeight) * 3;
+	for(i = 0; i < NFrames; i++){
+		v = emallocz(sizeof(VFrame) + 60 + frsz, 1);
+		sprint((char*)&v[1], "%11s %11d %11d %11d %11d ", "b8g8r8", 0, 0, GET2(f->wWidth), GET2(f->wHeight));
+		v->d = (uchar*)&v[1];
+		v->sz = frsz;
+		v->n = 60;
+		v->next = c->freel;
+		c->freel = v;
+	}
+}
+
+int
+videoopen(Cam *c, int fr)
+{
+	Dev *d;
+	Altc *altc;
+	Dev *ep;
+	Format *f;
+
+	qlock(&c->qulock);
+	if(c->active){
+		qunlock(&c->qulock);
+		werrstr("already in use");
+		return -1;
+	}
+	if(getframedesc(c, c->pc.bFormatIndex, c->pc.bFrameIndex, &f, nil) < 0){
+err:
+		qunlock(&c->qulock);
+		return -1;
+	}
+	if(getconverter(f) == nil) goto err;
+	d = c->dev;
+	if(usbcmd(d, 0x01, Rsetiface, 0, c->iface->id, nil, 0) < 0) goto err;
+	if(usbcmd(d, 0x21, SET_CUR, VS_PROBE_CONTROL << 8, c->iface->id, (uchar *) &c->pc, sizeof(ProbeControl)) < sizeof(ProbeControl)) goto err;
+	if(usbcmd(d, 0xA1, GET_CUR, VS_PROBE_CONTROL << 8, c->iface->id, (uchar *) &c->pc, sizeof(ProbeControl)) < 0) goto err;
+	if(usbcmd(d, 0x21, SET_CUR, VS_COMMIT_CONTROL << 8, c->iface->id, (uchar *) &c->pc, sizeof(ProbeControl)) < sizeof(ProbeControl)) goto err;
+	altc = selaltc(c, &c->pc);
+	if(altc == nil)
+		goto err;
+	ep = openep(d, c->hdr->bEndpointAddress & 0x7f);
+	if(ep == nil){
+		usbcmd(d, 0x01, Rsetiface, 0, c->iface->id, nil, 0);
+		goto err;
+	}
+	devctl(ep, "pollival %d", altc->interval);
+	devctl(ep, "uframes 1");
+	devctl(ep, "ntds %d", altc->ntds);
+	devctl(ep, "maxpkt %d", altc->maxpkt);
+	if(opendevdata(ep, OREAD) < 0){
+		usbcmd(d, 0x01, Rsetiface, 0, c->iface->id, nil, 0);
+		closedev(ep);
+		goto err;
+	}
+	c->ep = ep;
+	mkframes(c);
+	c->active = 1;
+	c->framemode = fr;
+	qunlock(&c->qulock);
+	c->cvtid = proccreate(cvtproc, c, 16384);
+	return 0;
+}
+
+void
+videoclose(Cam *c)
+{
+	if(c->active == 0 || c->abort)
+		return;
+	c->abort = -1;
+}
+
+void
+videoread(Req *r, Cam *c, int lock)
+{
+	VFrame *v;
+	int n;
+	Req **rp;
+
+	if(lock) qlock(&c->qulock);
+	if(c->active == 0 || c->abort){
+		if(lock) qunlock(&c->qulock);
+		respond(r, "the front fell off");
+		return;
+	}
+	if(c->framemode == 2){
+		c->framemode = 1;
+		if(lock) qunlock(&c->qulock);
+		r->ofcall.count = 0;
+		respond(r, nil);
+		return;
+	}
+	if(c->actl == nil){
+		for(rp = &c->delreq; *rp != nil; rp = (Req**)&(*rp)->qu.next)
+			;
+		r->qu.next = nil;
+		*rp = r;
+		if(lock) qunlock(&c->qulock);
+		return;
+	}
+	v = c->actl;
+	n = v->n - v->p;
+	if(n > r->ifcall.count) n = r->ifcall.count;
+	memcpy(r->ofcall.data, v->d + v->p, n);
+	v->p += n;
+	if(v->p == v->n){
+		if(c->framemode)
+			c->framemode = 2;
+		c->actl = v->next;
+		v->next = c->freel;
+		c->freel = v;
+	}
+	if(lock) qunlock(&c->qulock);
+	r->ofcall.count = n;
+	respond(r, nil);
+}
+
+void
+videoflush(Req *r, Cam *c)
+{
+	Req **rp;
+
+	qlock(&c->qulock);
+	for(rp = &c->delreq; *rp != nil; rp = (Req**)&(*rp)->qu.next)
+		if(*rp == r){
+			*rp = (Req *) r->qu.next;
+			respond(r, "interrupted");
+			break;
+		}
+	qunlock(&c->qulock);
+}
--- a/sys/src/cmd/nusb/mkfile
+++ b/sys/src/cmd/nusb/mkfile
@@ -10,6 +10,7 @@
 	serial\
 	ptp\
 	joy\
+	cam\
 
 UPDATE=\
 	mkfile\