summaryrefslogtreecommitdiff
path: root/sys/src/cmd/cdfs
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/cdfs
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/cdfs')
-rwxr-xr-xsys/src/cmd/cdfs/buf.c115
-rwxr-xr-xsys/src/cmd/cdfs/dat.h244
-rwxr-xr-xsys/src/cmd/cdfs/fns.h9
-rwxr-xr-xsys/src/cmd/cdfs/main.c729
-rwxr-xr-xsys/src/cmd/cdfs/mkfile16
-rwxr-xr-xsys/src/cmd/cdfs/mmc.c1543
6 files changed, 2656 insertions, 0 deletions
diff --git a/sys/src/cmd/cdfs/buf.c b/sys/src/cmd/cdfs/buf.c
new file mode 100755
index 000000000..245cdf325
--- /dev/null
+++ b/sys/src/cmd/cdfs/buf.c
@@ -0,0 +1,115 @@
+/*
+ * Buffered I/O on block devices.
+ * Write buffering ignores offset.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <disk.h>
+#include "dat.h"
+#include "fns.h"
+
+Buf*
+bopen(long (*fn)(Buf*, void*, long, ulong), int omode, int bs, int nblock)
+{
+ Buf *b;
+
+ assert(omode == OREAD || OWRITE);
+ assert(bs > 0 && nblock > 0);
+ assert(fn != nil);
+
+ b = emalloc(sizeof(*b));
+ b->data = emalloc(bs*nblock);
+ b->ndata = 0;
+ b->nblock = nblock;
+ b->bs = bs;
+ b->omode = omode;
+ b->fn = fn; /* function to read or write bs-byte blocks */
+
+ return b;
+}
+
+long
+bread(Buf *b, void *v, long n, vlong off)
+{
+ long m;
+ vlong noff;
+
+ assert(b->omode == OREAD);
+
+ /* Refill buffer */
+ if(b->off > off || off >= b->off+b->ndata) {
+ noff = off - off % b->bs;
+ if(vflag)
+ fprint(2, "try refill at %lld...", noff);
+ if((m = b->fn(b, b->data, b->nblock, noff/b->bs)) <= 0) {
+ if (vflag)
+ fprint(2, "failed\n");
+ return m;
+ }
+ b->ndata = b->bs * m;
+ b->off = noff;
+ if(vflag)
+ fprint(2, "got %ld\n", b->ndata);
+ }
+
+// fprint(2, "read %ld at %ld\n", n, off);
+ /* Satisfy request from buffer */
+ off -= b->off;
+ if(n > b->ndata - off)
+ n = b->ndata - off;
+ memmove(v, b->data+off, n);
+ return n;
+}
+
+long
+bwrite(Buf *b, void *v, long n)
+{
+ long on, m, mdata;
+ uchar *p;
+
+ p = v;
+ on = n;
+
+ /* Fill buffer */
+ mdata = b->bs*b->nblock;
+ m = mdata - b->ndata;
+ if(m > n)
+ m = n;
+ memmove(b->data+b->ndata, p, m);
+ p += m;
+ n -= m;
+ b->ndata += m;
+
+ /* Flush buffer */
+ if(b->ndata == mdata) {
+ if(b->fn(b, b->data, b->nblock, 0) < 0) {
+ if(vflag)
+ fprint(2, "write fails: %r\n");
+ return -1;
+ }
+ b->ndata = 0;
+ }
+
+ /* For now, don't worry about big writes; 9P only does 8k */
+ assert(n < mdata);
+
+ /* Add remainder to buffer */
+ if(n) {
+ memmove(b->data, p, n);
+ b->ndata = n;
+ }
+
+ return on;
+}
+
+void
+bterm(Buf *b)
+{
+ /* DVD & BD prefer full ecc blocks (tracks), but can cope with less */
+ if(b->omode == OWRITE && b->ndata)
+ b->fn(b, b->data, (b->ndata + b->bs - 1)/b->bs, 0);
+
+ free(b->data);
+ free(b);
+}
diff --git a/sys/src/cmd/cdfs/dat.h b/sys/src/cmd/cdfs/dat.h
new file mode 100755
index 000000000..8408ecccf
--- /dev/null
+++ b/sys/src/cmd/cdfs/dat.h
@@ -0,0 +1,244 @@
+enum {
+ Maxtrack = 200,
+ Ntrack = Maxtrack+1,
+ BScdrom = 2048, /* mmc data block size */
+ BScdda = 2352,
+ BScdxa = 2336,
+ BSmax = 2352,
+
+ /* scsi peripheral device types, SPC-3 §6.4.2 */
+ TypeDA = 0, /* Direct Access (SBC) */
+ TypeSA = 1, /* Sequential Access (SSC) */
+ TypeWO = 4, /* Worm (SBC)*/
+ TypeCD = 5, /* CD/DVD/BD (MMC) */
+ TypeMO = 7, /* rewriteable Magneto-Optical (SBC) */
+ TypeMC = 8, /* Medium Changer (SMC) */
+
+ /* MMC device types */
+ Mmcnone = 0,
+ Mmccd,
+ Mmcdvdminus,
+ Mmcdvdplus,
+ Mmcbd,
+
+ /* disc or track types */
+ TypeNone = 0,
+ TypeAudio,
+ TypeAwritable,
+ TypeData,
+ TypeDwritable,
+ TypeDisk,
+ TypeBlank,
+
+ /* offsets in Pagcapmechsts mode page; see MMC-3 §5.5.10 */
+ Capread = 2,
+ Capwrite = 3,
+ Capmisc = 5,
+
+ /* device capabilities in Pagcapmechsts mode page */
+ Capcdr = 1<<0, /* bytes 2 & 3 */
+ Capcdrw = 1<<1,
+ Captestwr = 1<<2,
+ Capdvdrom = 1<<3,
+ Capdvdr = 1<<4,
+ Capdvdram = 1<<5,
+ Capcdda = 1<<0, /* Capmisc bits */
+ Caprw = 1<<2,
+
+ /* Pagwrparams mode page offsets */
+ Wpwrtype = 2, /* write type */
+ Wptrkmode, /* track mode */
+ Wpdatblktype,
+ Wpsessfmt = 8,
+ Wppktsz = 10, /* BE ulong: # user data blks/fixed pkt */
+
+ /* Pagwrparams bits */
+ Bufe = 1<<6, /* Wpwrtype: buffer under-run free recording enable */
+ /* Wptrkmode */
+ Msbits = 3<<6, /* multisession field */
+ Msnonext= 0<<6, /* no next border nor session */
+ Mscdnonext= 1<<6, /* cd special: no next session */
+ Msnext = 3<<6, /* next session or border allowed */
+ Fp = 1<<5, /* pay attention to Wppktsz */
+
+ /* close track session cdb bits */
+ Closetrack = 1,
+ Closesessfinal = 2, /* close session / finalize disc */
+ Closefinaldvdrw = 3, /* dvd-rw special: finalize */
+ /* dvd+r dl special: close session, write extended lead-out */
+ Closesessextdvdrdl = 4,
+ Closefinal30mm = 5, /* dvd+r special: finalize with ≥30mm radius */
+ Closedvdrbdfinal= 6, /* dvd+r, bd-r special: finalize */
+
+ /* read toc format values */
+ Tocfmttoc = 0,
+ Tocfmtsessnos = 1,
+ Tocfmtqleadin = 2,
+ Tocfmtqpma = 3,
+ Tocfmtatip = 4,
+ Tocfmtcdtext = 5,
+
+ /* read toc cdb[1] bit */
+ Msfbit = 1<<1,
+
+ /* write types, MMC-6 §7.5.4.9 */
+ Wtpkt = 0, /* a.k.a. incremental */
+ Wttrackonce,
+ Wtsessonce, /* a.k.a. disc-at-once */
+ Wtraw,
+ Wtlayerjump,
+
+ /* track modes (TODO: also track types?) */
+ Tmcdda = 0, /* audio cdda */
+ Tm2audio, /* 2 audio channels */
+ Tmunintr = 4, /* data, recorded uninterrupted */
+ Tmintr, /* data, recorded interrupted (dvd default) */
+
+ /* data block types */
+ Dbraw = 0, /* 2352 bytes */
+ Db2kdata = 8, /* mode 1: 2K of user data */
+ Db2336, /* mode 2: 2336 bytes of user data */
+
+ /* session formats */
+ Sfdata = 0,
+ Sfcdi = 0x10,
+ Sfcdxa = 0x20,
+
+ /* Cache control bits in mode page 8 byte 2 */
+ Ccrcd = 1<<0, /* read cache disabled */
+ Ccmf = 1<<1, /* multiplication factor */
+ Ccwce = 1<<2, /* writeback cache enabled */
+ Ccsize = 1<<3, /* use `cache segment size', not `# of cache segments' */
+ Ccdisc = 1<<4, /* discontinuity */
+ Cccap = 1<<5, /* caching analysis permitted */
+ Ccabpf = 1<<6, /* abort pre-fetch */
+ Ccic = 1<<7, /* initiator control */
+
+ /* drive->cap bits */
+ Cwrite = 1<<0,
+ Ccdda = 1<<1,
+
+ CDNblock = 12, /* chosen for CD */
+ DVDNblock = 16, /* DVD ECC block is 16 sectors */
+ BDNblock = 32, /* BD ECC block (`cluster') is 32 sectors */
+ /*
+ * make a single transfer fit in a 9P rpc. if we don't do this,
+ * remote access (e.g., via /mnt/term/dev/sd*) fails mysteriously.
+ */
+ Readblock = 8192/BScdrom,
+};
+
+typedef struct Buf Buf;
+typedef struct Drive Drive;
+typedef struct Track Track;
+typedef struct Otrack Otrack;
+typedef struct Dev Dev;
+typedef struct Msf Msf; /* minute, second, frame */
+
+struct Msf {
+ int m;
+ int s;
+ int f;
+};
+
+struct Track
+{
+ /* initialized while obtaining the toc (gettoc) */
+ vlong size; /* total size in bytes */
+ long bs; /* block size in bytes */
+ ulong beg; /* beginning block number */
+ ulong end; /* ending block number */
+ int type;
+ Msf mbeg;
+ Msf mend;
+
+ /* initialized by fs */
+ char name[32];
+ int mode;
+ ulong mtime;
+};
+
+struct DTrack /* not used */
+{
+ uchar name[32];
+ uchar beg[4]; /* msf value; only used for audio */
+ uchar end[4]; /* msf value; only used for audio */
+ uchar size[8];
+ uchar magic[4];
+};
+
+struct Otrack
+{
+ Track *track;
+ Drive *drive;
+ int nchange;
+ int omode;
+ Buf *buf;
+
+ int nref; /* kept by file server */
+};
+
+struct Dev
+{
+ Otrack* (*openrd)(Drive *d, int trackno);
+ Otrack* (*create)(Drive *d, int bs);
+ long (*read)(Otrack *t, void *v, long n, vlong off);
+ long (*write)(Otrack *t, void *v, long n);
+ void (*close)(Otrack *t);
+ int (*gettoc)(Drive*);
+ int (*fixate)(Drive *d);
+ char* (*ctl)(Drive *d, int argc, char **argv);
+ char* (*setspeed)(Drive *d, int r, int w);
+};
+
+struct Drive
+{
+ QLock;
+ Scsi;
+
+ int type; /* scsi peripheral device type */
+
+ /* disc characteristics */
+ int mmctype;
+ char *dvdtype;
+ int firsttrack;
+ int ntrack;
+ int nchange; /* compare with the members in Scsi */
+ ulong changetime; /* " */
+ int nameok;
+ int writeok;
+ int blank; /* (not used for anything yet) */
+ int blankset;
+ int recordable; /* writable by burning? */
+ int recordableset;
+ int erasable; /* rewritable? */
+ int erasableset;
+
+ Track track[Ntrack];
+ ulong cap; /* drive capabilities */
+ uchar blkbuf[BScdda];
+
+ int maxreadspeed;
+ int maxwritespeed;
+ int readspeed;
+ int writespeed;
+ Dev;
+
+ void *aux; /* kept by driver */
+};
+
+struct Buf
+{
+ uchar *data; /* buffer */
+ vlong off; /* data[0] at offset off in file */
+ int bs; /* block size */
+ long ndata; /* no. valid bytes in data */
+ int nblock; /* total buffer size in blocks */
+ int omode; /* OREAD, OWRITE */
+ long (*fn)(Buf*, void*, long, ulong); /* read, write */
+
+ /* used only by client */
+ Otrack *otrack;
+};
+
+extern int vflag;
diff --git a/sys/src/cmd/cdfs/fns.h b/sys/src/cmd/cdfs/fns.h
new file mode 100755
index 000000000..ef4f6742b
--- /dev/null
+++ b/sys/src/cmd/cdfs/fns.h
@@ -0,0 +1,9 @@
+Buf* bopen(long (*)(Buf*, void*, long, ulong), int, int, int);
+long bread(Buf*, void*, long, vlong);
+void bterm(Buf*);
+long bufread(Otrack*, void*, long, vlong);
+long bufwrite(Otrack*, void*, long);
+long bwrite(Buf*, void*, long);
+void *emalloc(ulong);
+char* geterrstr(void);
+Drive* mmcprobe(Scsi*);
diff --git a/sys/src/cmd/cdfs/main.c b/sys/src/cmd/cdfs/main.c
new file mode 100755
index 000000000..cf3a3ed28
--- /dev/null
+++ b/sys/src/cmd/cdfs/main.c
@@ -0,0 +1,729 @@
+/* cdfs - CD, DVD and BD reader and writer file system */
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <disk.h>
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Aux Aux;
+struct Aux {
+ int doff;
+ Otrack *o;
+};
+
+ulong getnwa(Drive *);
+
+static void checktoc(Drive*);
+
+int vflag;
+
+static Drive *drive;
+static int nchange;
+
+enum {
+ Qdir = 0,
+ Qctl = 1,
+ Qwa = 2,
+ Qwd = 3,
+ Qtrack = 4,
+};
+
+char*
+geterrstr(void)
+{
+ static char errbuf[ERRMAX];
+
+ rerrstr(errbuf, sizeof errbuf);
+ return errbuf;
+}
+
+void*
+emalloc(ulong sz)
+{
+ void *v;
+
+ v = mallocz(sz, 1);
+ if(v == nil)
+ sysfatal("malloc %lud fails", sz);
+ return v;
+}
+
+static void
+fsattach(Req *r)
+{
+ char *spec;
+
+ spec = r->ifcall.aname;
+ if(spec && spec[0]) {
+ respond(r, "invalid attach specifier");
+ return;
+ }
+
+ checktoc(drive);
+ r->fid->qid = (Qid){Qdir, drive->nchange, QTDIR};
+ r->ofcall.qid = r->fid->qid;
+ r->fid->aux = emalloc(sizeof(Aux));
+ respond(r, nil);
+}
+
+static char*
+fsclone(Fid *old, Fid *new)
+{
+ Aux *na;
+
+ na = emalloc(sizeof(Aux));
+ *na = *((Aux*)old->aux);
+ if(na->o)
+ na->o->nref++;
+ new->aux = na;
+ return nil;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+ int i;
+
+ checktoc(drive);
+ switch((ulong)fid->qid.path) {
+ case Qdir:
+ if(strcmp(name, "..") == 0) {
+ *qid = (Qid){Qdir, drive->nchange, QTDIR};
+ return nil;
+ }
+ if(strcmp(name, "ctl") == 0) {
+ *qid = (Qid){Qctl, 0, 0};
+ return nil;
+ }
+ if(strcmp(name, "wa") == 0 && drive->writeok &&
+ (drive->mmctype == Mmcnone ||
+ drive->mmctype == Mmccd)) {
+ *qid = (Qid){Qwa, drive->nchange, QTDIR};
+ return nil;
+ }
+ if(strcmp(name, "wd") == 0 && drive->writeok) {
+ *qid = (Qid){Qwd, drive->nchange, QTDIR};
+ return nil;
+ }
+ for(i=0; i<drive->ntrack; i++)
+ if(strcmp(drive->track[i].name, name) == 0)
+ break;
+ if(i == drive->ntrack)
+ return "file not found";
+ *qid = (Qid){Qtrack+i, 0, 0};
+ return nil;
+
+ case Qwa:
+ case Qwd:
+ if(strcmp(name, "..") == 0) {
+ *qid = (Qid){Qdir, drive->nchange, QTDIR};
+ return nil;
+ }
+ return "file not found";
+ default: /* bug: lib9p could handle this */
+ return "walk in non-directory";
+ }
+}
+
+static void
+fscreate(Req *r)
+{
+ int omode, type;
+ Otrack *o;
+ Fid *fid;
+
+ fid = r->fid;
+ omode = r->ifcall.mode;
+
+ if(omode != OWRITE) {
+ respond(r, "bad mode (use OWRITE)");
+ return;
+ }
+
+ switch((ulong)fid->qid.path) {
+ case Qdir:
+ default:
+ respond(r, "permission denied");
+ return;
+
+ case Qwa:
+ if (drive->mmctype != Mmcnone &&
+ drive->mmctype != Mmccd) {
+ respond(r, "audio supported only on cd");
+ return;
+ }
+ type = TypeAudio;
+ break;
+
+ case Qwd:
+ type = TypeData;
+ break;
+ }
+
+ if((drive->cap & Cwrite) == 0) {
+ respond(r, "drive does not write");
+ return;
+ }
+
+ o = drive->create(drive, type);
+ if(o == nil) {
+ respond(r, geterrstr());
+ return;
+ }
+ drive->nchange = -1;
+ checktoc(drive); /* update directory info */
+ o->nref = 1;
+ ((Aux*)fid->aux)->o = o;
+
+ fid->qid = (Qid){Qtrack+(o->track - drive->track), drive->nchange, 0};
+ r->ofcall.qid = fid->qid;
+ respond(r, nil);
+}
+
+static void
+fsremove(Req *r)
+{
+ switch((ulong)r->fid->qid.path){
+ case Qwa:
+ case Qwd:
+ if(drive->fixate(drive) < 0)
+ respond(r, geterrstr());
+// let us see if it can figure this out: drive->writeok = 0;
+ else
+ respond(r, nil);
+ checktoc(drive);
+ break;
+ default:
+ respond(r, "permission denied");
+ break;
+ }
+}
+
+/* result is one word, so it can be used as a uid in Dir structs */
+static char *
+disctype(Drive *drive)
+{
+ char *type, *rw;
+
+ switch (drive->mmctype) {
+ case Mmccd:
+ type = "cd-";
+ break;
+ case Mmcdvdminus:
+ case Mmcdvdplus:
+ type = drive->dvdtype;
+ break;
+ case Mmcbd:
+ type = "bd-";
+ break;
+ case Mmcnone:
+ type = "no-disc";
+ break;
+ default:
+ type = "**GOK**"; /* traditional */
+ break;
+ }
+ rw = "";
+ if (drive->mmctype != Mmcnone && drive->dvdtype == nil)
+ if (drive->erasable)
+ rw = drive->mmctype == Mmcbd? "re": "rw";
+ else if (drive->recordable)
+ rw = "r";
+ else
+ rw = "rom";
+ return smprint("%s%s", type, rw);
+}
+
+int
+fillstat(ulong qid, Dir *d)
+{
+ char *ty;
+ Track *t;
+ static char buf[32];
+
+ nulldir(d);
+ d->type = L'M';
+ d->dev = 1;
+ d->length = 0;
+ ty = disctype(drive);
+ strncpy(buf, ty, sizeof buf);
+ free(ty);
+ d->uid = d->gid = buf;
+ d->muid = "";
+ d->qid = (Qid){qid, drive->nchange, 0};
+ d->atime = time(0);
+ d->mtime = drive->changetime;
+
+ switch(qid){
+ case Qdir:
+ d->name = "/";
+ d->qid.type = QTDIR;
+ d->mode = DMDIR|0777;
+ break;
+
+ case Qctl:
+ d->name = "ctl";
+ d->mode = 0666;
+ break;
+
+ case Qwa:
+ if(drive->writeok == 0 ||
+ drive->mmctype != Mmcnone &&
+ drive->mmctype != Mmccd)
+ return 0;
+ d->name = "wa";
+ d->qid.type = QTDIR;
+ d->mode = DMDIR|0777;
+ break;
+
+ case Qwd:
+ if(drive->writeok == 0)
+ return 0;
+ d->name = "wd";
+ d->qid.type = QTDIR;
+ d->mode = DMDIR|0777;
+ break;
+
+ default:
+ if(qid-Qtrack >= drive->ntrack)
+ return 0;
+ t = &drive->track[qid-Qtrack];
+ if(strcmp(t->name, "") == 0)
+ return 0;
+ d->name = t->name;
+ d->mode = t->mode;
+ d->length = t->size;
+ break;
+ }
+ return 1;
+}
+
+static ulong
+cddb_sum(int n)
+{
+ int ret;
+ ret = 0;
+ while(n > 0) {
+ ret += n%10;
+ n /= 10;
+ }
+ return ret;
+}
+
+static ulong
+diskid(Drive *d)
+{
+ int i, n;
+ ulong tmp;
+ Msf *ms, *me;
+
+ n = 0;
+ for(i=0; i < d->ntrack; i++)
+ n += cddb_sum(d->track[i].mbeg.m*60+d->track[i].mbeg.s);
+
+ ms = &d->track[0].mbeg;
+ me = &d->track[d->ntrack].mbeg;
+ tmp = (me->m*60+me->s) - (ms->m*60+ms->s);
+
+ /*
+ * the spec says n%0xFF rather than n&0xFF. it's unclear which is
+ * correct. most CDs are in the database under both entries.
+ */
+ return ((n % 0xFF) << 24 | (tmp << 8) | d->ntrack);
+}
+
+static void
+readctl(Req *r)
+{
+ int i, isaudio;
+ char *p, *e, *ty;
+ char s[1024];
+ Msf *m;
+
+ isaudio = 0;
+ for(i=0; i<drive->ntrack; i++)
+ if(drive->track[i].type == TypeAudio)
+ isaudio = 1;
+
+ p = s;
+ e = s + sizeof s;
+ *p = '\0';
+ if(isaudio){
+ p = seprint(p, e, "aux/cddb query %8.8lux %d", diskid(drive),
+ drive->ntrack);
+ for(i=0; i<drive->ntrack; i++){
+ m = &drive->track[i].mbeg;
+ p = seprint(p, e, " %d", (m->m*60 + m->s)*75 + m->f);
+ }
+ m = &drive->track[drive->ntrack].mbeg;
+ p = seprint(p, e, " %d\n", m->m*60 + m->s);
+ }
+
+ if(drive->readspeed == drive->writespeed)
+ p = seprint(p, e, "speed %d\n", drive->readspeed);
+ else
+ p = seprint(p, e, "speed read %d write %d\n",
+ drive->readspeed, drive->writespeed);
+ p = seprint(p, e, "maxspeed read %d write %d\n",
+ drive->maxreadspeed, drive->maxwritespeed);
+
+ if (drive->Scsi.changetime != 0 && drive->ntrack != 0) { /* have disc? */
+ ty = disctype(drive);
+ p = seprint(p, e, "%s", ty);
+ free(ty);
+ if (drive->mmctype != Mmcnone)
+ p = seprint(p, e, " next writable sector %lud",
+ getnwa(drive));
+ seprint(p, e, "\n");
+ }
+ readstr(r, s);
+}
+
+static void
+fsread(Req *r)
+{
+ int j, n, m;
+ uchar *p, *ep;
+ Dir d;
+ Fid *fid;
+ Otrack *o;
+ vlong offset;
+ void *buf;
+ long count;
+ Aux *a;
+
+ fid = r->fid;
+ offset = r->ifcall.offset;
+ buf = r->ofcall.data;
+ count = r->ifcall.count;
+
+ switch((ulong)fid->qid.path) {
+ case Qdir:
+ checktoc(drive);
+ p = buf;
+ ep = p+count;
+ m = Qtrack+drive->ntrack;
+ a = fid->aux;
+ if(offset == 0)
+ a->doff = 1; /* skip root */
+
+ for(j=a->doff; j<m; j++) {
+ if(fillstat(j, &d)) {
+ if((n = convD2M(&d, p, ep-p)) <= BIT16SZ)
+ break;
+ p += n;
+ }
+ }
+ a->doff = j;
+
+ r->ofcall.count = p - (uchar*)buf;
+ break;
+ case Qwa:
+ case Qwd:
+ r->ofcall.count = 0;
+ break;
+ case Qctl:
+ readctl(r);
+ break;
+ default:
+ /* a disk track; we can only call read for whole blocks */
+ o = ((Aux*)fid->aux)->o;
+ if((count = o->drive->read(o, buf, count, offset)) < 0) {
+ respond(r, geterrstr());
+ return;
+ }
+ r->ofcall.count = count;
+ break;
+ }
+ respond(r, nil);
+}
+
+static char Ebadmsg[] = "bad cdfs control message";
+
+static char*
+writectl(void *v, long count)
+{
+ char buf[256];
+ char *f[10], *p;
+ int i, nf, n, r, w, what;
+
+ if(count >= sizeof(buf))
+ count = sizeof(buf)-1;
+ memmove(buf, v, count);
+ buf[count] = '\0';
+
+ nf = tokenize(buf, f, nelem(f));
+ if(nf == 0)
+ return Ebadmsg;
+
+ if(strcmp(f[0], "speed") == 0){
+ what = 0;
+ r = w = -1;
+ if(nf == 1)
+ return Ebadmsg;
+ for(i=1; i<nf; i++){
+ if(strcmp(f[i], "read") == 0 || strcmp(f[i], "write") == 0){
+ if(what!=0 && what!='?')
+ return Ebadmsg;
+ what = f[i][0];
+ }else{
+ if (strcmp(f[i], "best") == 0)
+ n = (1<<16) - 1;
+ else {
+ n = strtol(f[i], &p, 0);
+ if(*p != '\0' || n <= 0)
+ return Ebadmsg;
+ }
+ switch(what){
+ case 0:
+ if(r >= 0 || w >= 0)
+ return Ebadmsg;
+ r = w = n;
+ break;
+ case 'r':
+ if(r >= 0)
+ return Ebadmsg;
+ r = n;
+ break;
+ case 'w':
+ if(w >= 0)
+ return Ebadmsg;
+ w = n;
+ break;
+ default:
+ return Ebadmsg;
+ }
+ what = '?';
+ }
+ }
+ if(what != '?')
+ return Ebadmsg;
+ return drive->setspeed(drive, r, w);
+ }
+ return drive->ctl(drive, nf, f);
+}
+
+static void
+fswrite(Req *r)
+{
+ Otrack *o;
+ Fid *fid;
+
+ fid = r->fid;
+ r->ofcall.count = r->ifcall.count;
+ if(fid->qid.path == Qctl) {
+ respond(r, writectl(r->ifcall.data, r->ifcall.count));
+ return;
+ }
+
+ if((o = ((Aux*)fid->aux)->o) == nil || o->omode != OWRITE) {
+ respond(r, "permission denied");
+ return;
+ }
+
+ if(o->drive->write(o, r->ifcall.data, r->ifcall.count) < 0)
+ respond(r, geterrstr());
+ else
+ respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+ fillstat((ulong)r->fid->qid.path, &r->d);
+ r->d.name = estrdup9p(r->d.name);
+ r->d.uid = estrdup9p(r->d.uid);
+ r->d.gid = estrdup9p(r->d.gid);
+ r->d.muid = estrdup9p(r->d.muid);
+ respond(r, nil);
+}
+
+static void
+fsopen(Req *r)
+{
+ int omode;
+ Fid *fid;
+ Otrack *o;
+
+ fid = r->fid;
+ omode = r->ifcall.mode;
+ checktoc(drive);
+ r->ofcall.qid = (Qid){fid->qid.path, drive->nchange, fid->qid.vers};
+
+ switch((ulong)fid->qid.path){
+ case Qdir:
+ case Qwa:
+ case Qwd:
+ if(omode != OREAD) {
+ respond(r, "permission denied");
+ return;
+ }
+ break;
+ case Qctl:
+ if(omode & ~(OTRUNC|OREAD|OWRITE|ORDWR)) {
+ respond(r, "permission denied");
+ return;
+ }
+ break;
+ default:
+ if(fid->qid.path >= Qtrack+drive->ntrack) {
+ respond(r, "file no longer exists");
+ return;
+ }
+
+ /*
+ * allow the open with OWRITE or ORDWR if the
+ * drive and disc are both capable?
+ */
+ if(omode != OREAD ||
+ (o = drive->openrd(drive, fid->qid.path-Qtrack)) == nil) {
+ respond(r, "permission denied");
+ return;
+ }
+
+ o->nref = 1;
+ ((Aux*)fid->aux)->o = o;
+ break;
+ }
+ respond(r, nil);
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+ Aux *aux;
+ Otrack *o;
+
+ aux = fid->aux;
+ if(aux == nil)
+ return;
+ o = aux->o;
+ if(o && --o->nref == 0) {
+ bterm(o->buf);
+ drive->close(o);
+ checktoc(drive);
+ }
+}
+
+static void
+checktoc(Drive *drive)
+{
+ int i;
+ Track *t;
+
+ drive->gettoc(drive);
+ if(drive->nameok)
+ return;
+
+ for(i=0; i<drive->ntrack; i++) {
+ t = &drive->track[i];
+ if(t->size == 0) /* being created */
+ t->mode = 0;
+ else
+ t->mode = 0444;
+ sprint(t->name, "?%.3d", i);
+ switch(t->type){
+ case TypeNone:
+ t->name[0] = 'u';
+// t->mode = 0;
+ break;
+ case TypeData:
+ t->name[0] = 'd';
+ break;
+ case TypeAudio:
+ t->name[0] = 'a';
+ break;
+ case TypeBlank:
+ t->name[0] = '\0';
+ break;
+ default:
+ print("unknown track type %d\n", t->type);
+ break;
+ }
+ }
+
+ drive->nameok = 1;
+}
+
+long
+bufread(Otrack *t, void *v, long n, vlong off)
+{
+ return bread(t->buf, v, n, off);
+}
+
+long
+bufwrite(Otrack *t, void *v, long n)
+{
+ return bwrite(t->buf, v, n);
+}
+
+Srv fs = {
+.attach= fsattach,
+.destroyfid= fsdestroyfid,
+.clone= fsclone,
+.walk1= fswalk1,
+.open= fsopen,
+.read= fsread,
+.write= fswrite,
+.create= fscreate,
+.remove= fsremove,
+.stat= fsstat,
+};
+
+void
+usage(void)
+{
+ fprint(2, "usage: cdfs [-Dv] [-d /dev/sdC0] [-m mtpt]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ Scsi *s;
+ int fd;
+ char *dev, *mtpt;
+
+ dev = "/dev/sdD0";
+ mtpt = "/mnt/cd";
+
+ ARGBEGIN{
+ case 'D':
+ chatty9p++;
+ break;
+ case 'd':
+ dev = EARGF(usage());
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ case 'v':
+ if((fd = create("/tmp/cdfs.log", OWRITE, 0666)) >= 0) {
+ dup(fd, 2);
+ dup(fd, 1);
+ if(fd != 1 && fd != 2)
+ close(fd);
+ vflag++;
+ scsiverbose = 2; /* verbose but no Readtoc errs */
+ }
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(dev == nil || mtpt == nil || argc > 0)
+ usage();
+
+ if((s = openscsi(dev)) == nil)
+ sysfatal("openscsi '%s': %r", dev);
+ if((drive = mmcprobe(s)) == nil)
+ sysfatal("mmcprobe '%s': %r", dev);
+ checktoc(drive);
+
+ postmountsrv(&fs, nil, mtpt, MREPL|MCREATE);
+ exits(nil);
+}
diff --git a/sys/src/cmd/cdfs/mkfile b/sys/src/cmd/cdfs/mkfile
new file mode 100755
index 000000000..479e72a28
--- /dev/null
+++ b/sys/src/cmd/cdfs/mkfile
@@ -0,0 +1,16 @@
+</$objtype/mkfile
+
+TARG=cdfs
+
+OFILES=\
+ buf.$O\
+ main.$O\
+ mmc.$O\
+
+HFILES=\
+ dat.h\
+ fns.h\
+ ../scuzz/scsireq.h\
+
+BIN=/$objtype/bin
+</sys/src/cmd/mkone
diff --git a/sys/src/cmd/cdfs/mmc.c b/sys/src/cmd/cdfs/mmc.c
new file mode 100755
index 000000000..c2ee6caf2
--- /dev/null
+++ b/sys/src/cmd/cdfs/mmc.c
@@ -0,0 +1,1543 @@
+/*
+ * multi-media commands
+ *
+ * as of mmc-6, mode page 0x2a (capabilities & mechanical status) is legacy
+ * and read-only, last defined in mmc-3. mode page 5 (write parameters)
+ * applies only to cd-r(w) and dvd-r(w); *-rom, dvd+* and bd-* are right out.
+ */
+#include <u.h>
+#include <libc.h>
+#include <disk.h>
+#include "../scuzz/scsireq.h"
+#include "dat.h"
+#include "fns.h"
+
+enum
+{
+ Pagesz = 255,
+
+ Pagwrparams = 5, /* (cd|dvd)-r(w) device write parameters */
+ Pagcache = 8,
+ Pagcapmechsts = 0x2a,
+
+ Invistrack = 0xff, /* the invisible & incomplete track */
+};
+
+static Dev mmcdev;
+
+typedef struct Mmcaux Mmcaux;
+struct Mmcaux {
+ /* drive characteristics */
+ uchar page05[Pagesz]; /* write parameters */
+ int page05ok;
+ int pagecmdsz;
+
+ /* disc characteristics */
+ ulong mmcnwa;
+ int nropen;
+ int nwopen;
+ vlong ntotby;
+ long ntotbk;
+};
+
+/* these will be printed as user ids, so no spaces please */
+static char *dvdtype[] = {
+ "dvd-rom",
+ "dvd-ram",
+ "dvd-r",
+ "dvd-rw",
+ "hd-dvd-rom",
+ "hd-dvd-ram",
+ "hd-dvd-r",
+ "type-7-unknown",
+ "type-8-unknown",
+ "dvd+rw",
+ "dvd+r",
+ "type-11-unknown",
+ "type-12-unknown",
+ "dvd+rw-dl",
+ "dvd+r-dl",
+ "type-15-unknown",
+};
+
+static ulong
+bige(void *p)
+{
+ uchar *a;
+
+ a = p;
+ return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
+}
+
+static ushort
+biges(void *p)
+{
+ uchar *a;
+
+ a = p;
+ return (a[0]<<8) | a[1];
+}
+
+ulong
+getnwa(Drive *drive)
+{
+ Mmcaux *aux;
+
+ aux = drive->aux;
+ return aux->mmcnwa;
+}
+
+static void
+hexdump(void *v, int n)
+{
+ int i;
+ uchar *p;
+
+ p = v;
+ for(i=0; i<n; i++){
+ print("%.2ux ", p[i]);
+ if((i%8) == 7)
+ print("\n");
+ }
+ if(i%8)
+ print("\n");
+}
+
+static void
+initcdb(uchar *cdb, int len, int cmd)
+{
+ memset(cdb, 0, len);
+ cdb[0] = cmd;
+}
+
+//static uchar *
+//newcdb(int len, int cmd)
+//{
+// uchar *cdb;
+//
+// cdb = emalloc(len);
+// cdb[0] = cmd;
+// return cdb;
+//}
+
+/*
+ * SCSI CDBs (cmd arrays) are 6, 10, 12, 16 or 32 bytes long.
+ * The mode sense/select commands implicitly refer to
+ * a mode parameter list, which consists of an 8-byte
+ * mode parameter header, followed by zero or more block
+ * descriptors and zero or more mode pages (MMC-2 §5.5.2).
+ * We'll ignore mode sub-pages.
+ * Integers are stored big-endian.
+ *
+ * The format of the mode parameter (10) header is:
+ * ushort mode_data_length; // of following bytes
+ * uchar medium_type;
+ * uchar device_specific;
+ * uchar reserved[2];
+ * ushort block_descriptor_length; // zero
+ *
+ * The format of the mode parameter (6) header is:
+ * uchar mode_data_length; // of following bytes
+ * uchar medium_type;
+ * uchar device_specific;
+ * uchar block_descriptor_length; // zero
+ *
+ * The format of the mode pages is:
+ * uchar page_code_and_PS;
+ * uchar page_len; // of following bytes
+ * uchar parameter[page_len];
+ *
+ * see SPC-3 §4.3.4.6 for allocation length and §7.4 for mode parameter lists.
+ */
+
+enum {
+ Mode10parmhdrlen= 8,
+ Mode6parmhdrlen = 4,
+ Modepaghdrlen = 2,
+};
+
+static int
+mmcgetpage10(Drive *drive, int page, void *v)
+{
+ uchar cmd[10], resp[512];
+ int n, r;
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdMsense10;
+ cmd[2] = page;
+ cmd[8] = 255; /* allocation length: buffer size */
+
+// print("get: sending cmd\n");
+// hexdump(cmd, 10);
+
+ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
+ if(n < Mode10parmhdrlen)
+ return -1;
+
+ r = (resp[6]<<8) | resp[7]; /* block descriptor length */
+ n -= Mode10parmhdrlen + r;
+
+ if(n < 0)
+ return -1;
+ if(n > Pagesz)
+ n = Pagesz;
+
+ memmove(v, &resp[Mode10parmhdrlen + r], n);
+
+// print("get: got cmd\n");
+// hexdump(cmd, 10);
+// print("page\n");
+// hexdump(v, n);
+
+ return n;
+}
+
+static int
+mmcgetpage6(Drive *drive, int page, void *v)
+{
+ uchar cmd[6], resp[512];
+ int n;
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdMsense6;
+ cmd[2] = page;
+ cmd[4] = 255; /* allocation length */
+
+ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
+ if(n < Mode6parmhdrlen)
+ return -1;
+
+ n -= Mode6parmhdrlen + resp[3];
+ if(n < 0)
+ return -1;
+ if(n > Pagesz)
+ n = Pagesz;
+
+ memmove(v, &resp[Mode6parmhdrlen + resp[3]], n);
+ return n;
+}
+
+static int
+mmcsetpage10(Drive *drive, int page, void *v)
+{
+ uchar cmd[10], *p, *pagedata;
+ int len, n;
+
+ /* allocate parameter list, copy in mode page, fill in header */
+ pagedata = v;
+ assert(pagedata[0] == page);
+ len = Mode10parmhdrlen + Modepaghdrlen + pagedata[1];
+ p = emalloc(len);
+ memmove(p + Mode10parmhdrlen, pagedata, pagedata[1]);
+ /* parameter list header */
+ p[0] = 0;
+ p[1] = len - 2;
+
+ /* set up CDB */
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdMselect10;
+ cmd[1] = 0x10; /* format not vendor-specific */
+ cmd[8] = len;
+
+// print("set: sending cmd\n");
+// hexdump(cmd, 10);
+// print("parameter list header\n");
+// hexdump(p, Mode10parmhdrlen);
+// print("page\n");
+// hexdump(p + Mode10parmhdrlen, len - Mode10parmhdrlen);
+
+ n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite);
+
+// print("set: got cmd\n");
+// hexdump(cmd, 10);
+
+ free(p);
+ if(n < len)
+ return -1;
+ return 0;
+}
+
+static int
+mmcsetpage6(Drive *drive, int page, void *v)
+{
+ uchar cmd[6], *p, *pagedata;
+ int len, n;
+
+ if (vflag)
+ print("mmcsetpage6 called!\n");
+ pagedata = v;
+ assert(pagedata[0] == page);
+ len = Mode6parmhdrlen + Modepaghdrlen + pagedata[1];
+ p = emalloc(len);
+ memmove(p + Mode6parmhdrlen, pagedata, pagedata[1]);
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdMselect6;
+ cmd[1] = 0x10; /* format not vendor-specific */
+ cmd[4] = len;
+
+ n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite);
+ free(p);
+ if(n < len)
+ return -1;
+ return 0;
+}
+
+static int
+mmcgetpage(Drive *drive, int page, void *v)
+{
+ Mmcaux *aux;
+
+ aux = drive->aux;
+ switch(aux->pagecmdsz) {
+ case 10:
+ return mmcgetpage10(drive, page, v);
+ case 6:
+ return mmcgetpage6(drive, page, v);
+ default:
+ assert(0);
+ }
+ return -1;
+}
+
+static int
+mmcsetpage(Drive *drive, int page, void *v)
+{
+ Mmcaux *aux;
+
+ aux = drive->aux;
+ switch(aux->pagecmdsz) {
+ case 10:
+ return mmcsetpage10(drive, page, v);
+ case 6:
+ return mmcsetpage6(drive, page, v);
+ default:
+ assert(0);
+ }
+ return -1;
+}
+
+int
+mmcstatus(Drive *drive)
+{
+ uchar cmd[12];
+
+ initcdb(cmd, sizeof cmd, ScmdCDstatus); /* mechanism status */
+ return scsi(drive, cmd, sizeof(cmd), nil, 0, Sread);
+}
+
+void
+mmcgetspeed(Drive *drive)
+{
+ int n, maxread, curread, maxwrite, curwrite;
+ uchar buf[Pagesz];
+
+ memset(buf, 0, 22);
+ n = mmcgetpage(drive, Pagcapmechsts, buf); /* legacy page */
+ if (n < 22) {
+ if (vflag)
+ fprint(2, "no Pagcapmechsts mode page!\n");
+ return;
+ }
+ maxread = (buf[8]<<8)|buf[9];
+ curread = (buf[14]<<8)|buf[15];
+ maxwrite = (buf[18]<<8)|buf[19];
+ curwrite = (buf[20]<<8)|buf[21];
+
+ if(maxread && maxread < 170 || curread && curread < 170)
+ return; /* bogus data */
+
+ drive->readspeed = curread;
+ drive->writespeed = curwrite;
+ drive->maxreadspeed = maxread;
+ drive->maxwritespeed = maxwrite;
+}
+
+static int
+getdevtype(Drive *drive)
+{
+ int n;
+ uchar cmd[6], resp[Pagesz];
+
+ initcdb(cmd, sizeof cmd, ScmdInq);
+ cmd[3] = sizeof resp >> 8;
+ cmd[4] = sizeof resp;
+ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread);
+ if (n < 8)
+ return -1;
+ return resp[0] & 037;
+}
+
+static int
+start(Drive *drive, int code)
+{
+ uchar cmd[6];
+
+ initcdb(cmd, sizeof cmd, ScmdStart);
+ cmd[4] = code;
+ return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
+}
+
+Drive*
+mmcprobe(Scsi *scsi)
+{
+ Mmcaux *aux;
+ Drive *drive;
+ uchar buf[Pagesz];
+ int cap, n;
+
+ if (vflag)
+ print("mmcprobe: inquiry: %s\n", scsi->inquire);
+ drive = emalloc(sizeof(Drive));
+ drive->Scsi = *scsi;
+ drive->Dev = mmcdev;
+ aux = emalloc(sizeof(Mmcaux));
+ drive->aux = aux;
+ scsiready(drive);
+ drive->type = getdevtype(drive);
+ if (drive->type != TypeCD) {
+ werrstr("not an mmc device");
+ free(aux);
+ free(drive);
+ return nil;
+ }
+
+ /*
+ * drive is an mmc device; learn what we can about it
+ * (as opposed to the disc in it).
+ */
+
+ start(drive, 1);
+ /* attempt to read CD capabilities page, but it's now legacy */
+ if(mmcgetpage10(drive, Pagcapmechsts, buf) >= 0)
+ aux->pagecmdsz = 10;
+ else if(mmcgetpage6(drive, Pagcapmechsts, buf) >= 0)
+ aux->pagecmdsz = 6;
+ else {
+ if (vflag)
+ fprint(2, "no Pagcapmechsts mode page!\n");
+ werrstr("can't read mode page %d!", Pagcapmechsts);
+ free(aux);
+ free(drive);
+ return nil;
+ }
+
+ cap = 0;
+ if(buf[Capwrite] & (Capcdr|Capcdrw|Capdvdr|Capdvdram) ||
+ buf[Capmisc] & Caprw)
+ cap |= Cwrite;
+ if(buf[Capmisc] & Capcdda) /* CD-DA commands supported? */
+ cap |= Ccdda; /* not used anywhere else */
+
+// print("read %d max %d\n", biges(buf+14), biges(buf+8));
+// print("write %d max %d\n", biges(buf+20), biges(buf+18));
+
+ /* cache optional page 05 (write parameter page) */
+ if(/* (cap & Cwrite) && */
+ mmcgetpage(drive, Pagwrparams, aux->page05) >= 0) {
+ aux->page05ok = 1;
+ cap |= Cwrite;
+ if (vflag)
+ fprint(2, "mmcprobe: got page 5, assuming drive can write\n");
+ } else {
+ if (vflag)
+ fprint(2, "no Pagwrparams mode page!\n");
+ cap &= ~Cwrite;
+ }
+ drive->cap = cap;
+
+ mmcgetspeed(drive);
+
+ /*
+ * we can't actually control caching much.
+ * see SBC-2 §6.3.3 but also MMC-6 §7.6.
+ */
+ n = mmcgetpage(drive, Pagcache, buf);
+ if (n >= 3) {
+ /* n == 255; buf[1] == 10 (10 bytes after buf[1]) */
+ buf[0] &= 077; /* clear reserved bits, MMC-6 §7.2.3 */
+ assert(buf[0] == Pagcache);
+ assert(buf[1] >= 10);
+ buf[2] = Ccwce;
+ if (mmcsetpage(drive, Pagcache, buf) < 0)
+ if (vflag)
+ print("mmcprobe: cache control NOT set\n");
+ }
+ return drive;
+}
+
+static char *tracktype[] = { /* indices are track modes (Tm*) */
+ "audio cdda",
+ "2 audio channels",
+ "2",
+ "3",
+ "data, recorded uninterrupted",
+ "data, recorded interrupted",
+};
+
+/* t is a track number on disc, i is an index into drive->track[] for result */
+static int
+mmctrackinfo(Drive *drive, int t, int i)
+{
+ int n, type, bs;
+ ulong beg, size;
+ uchar tmode;
+ uchar cmd[10], resp[255];
+ Mmcaux *aux;
+
+ aux = drive->aux;
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdRtrackinfo;
+ cmd[1] = 1; /* address below is logical track # */
+ cmd[2] = t>>24;
+ cmd[3] = t>>16;
+ cmd[4] = t>>8;
+ cmd[5] = t;
+ cmd[7] = sizeof(resp)>>8;
+ cmd[8] = sizeof(resp);
+ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
+ if(n < 28) {
+ if(vflag)
+ print("trackinfo %d fails n=%d: %r\n", t, n);
+ return -1;
+ }
+
+ beg = bige(&resp[8]);
+ size = bige(&resp[24]);
+
+ tmode = resp[5] & 0x0D;
+// dmode = resp[6] & 0x0F;
+
+ if(vflag)
+ print("track %d type %d (%s)", t, tmode,
+ (tmode < nelem(tracktype)? tracktype[tmode]: "**GOK**"));
+ type = TypeNone;
+ bs = BScdda;
+ switch(tmode){
+ case Tmcdda:
+ type = TypeAudio;
+ bs = BScdda;
+ break;
+ case Tm2audio: /* 2 audio channels, with pre-emphasis 50/15 μs */
+ if(vflag)
+ print("audio channels with preemphasis on track %d "
+ "(u%.3d)\n", t, i);
+ type = TypeNone;
+ break;
+ case Tmunintr: /* data track, recorded uninterrupted */
+ case Tmintr: /* data track, recorded interrupted */
+ /* treat Tmintr (5) as cdrom; it's probably dvd or bd */
+ type = TypeData;
+ bs = BScdrom;
+ break;
+ default:
+ if(vflag)
+ print("unknown track type %d\n", tmode);
+ break;
+ }
+
+ drive->track[i].mtime = drive->changetime;
+ drive->track[i].beg = beg;
+ drive->track[i].end = beg+size;
+ drive->track[i].type = type;
+ drive->track[i].bs = bs;
+ drive->track[i].size = (vlong)(size-2) * bs; /* -2: skip lead out */
+
+ if(resp[6] & (1<<6)) { /* blank? */
+ drive->track[i].type = TypeBlank;
+ drive->writeok = 1;
+ }
+
+ if(vflag)
+ print(" start %lud end %lud", beg, beg + size - 1);
+ /* resp[6] & (1<<7) of zero: invisible track */
+ /* t == getinvistrack(): invisible track */
+ if(t == Invistrack || resp[7] & 1) { /* invis or nwa valid? */
+ aux->mmcnwa = bige(&resp[12]);
+ if ((long)aux->mmcnwa < 0) /* implausible? */
+ aux->mmcnwa = 0;
+ if (vflag)
+ print(" nwa %lud", aux->mmcnwa);
+ }
+ if (vflag)
+ print("\n");
+ return 0;
+}
+
+/* this may fail for blank media */
+static int
+mmcreadtoc(Drive *drive, int type, int track, void *data, int nbytes)
+{
+ uchar cmd[10];
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdRTOC;
+ cmd[1] = type; /* msf bit & reserved */
+ cmd[2] = Tocfmttoc;
+ cmd[6] = track; /* track/session */
+ cmd[7] = nbytes>>8;
+ cmd[8] = nbytes;
+
+ /*
+ * printing iounit(drive->Scsi.rawfd) here yields
+ * iounit(3) = 0; # for local access
+ * iounit(3) = 65512; # for remote access via /mnt/term
+ */
+ return scsi(drive, cmd, sizeof(cmd), data, nbytes, Sread);
+}
+
+static int
+mmcreaddiscinfo(Drive *drive, void *data, int nbytes)
+{
+ uchar cmd[10];
+ int n;
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdRdiscinfo;
+ cmd[7] = nbytes>>8;
+ cmd[8] = nbytes;
+ n = scsi(drive, cmd, sizeof(cmd), data, nbytes, Sread);
+ if(n < 24) {
+ if(n >= 0)
+ werrstr("rdiscinfo returns %d", n);
+ return -1;
+ }
+
+ return n;
+}
+
+static Msf
+rdmsf(uchar *p)
+{
+ Msf msf;
+
+ msf.m = p[0];
+ msf.s = p[1];
+ msf.f = p[2];
+ return msf;
+}
+
+static int
+getdiscinfo(Drive *drive, uchar resp[], int resplen)
+{
+ int n;
+
+ n = mmcreaddiscinfo(drive, resp, resplen);
+ if(n < 3) {
+ if (vflag)
+ fprint(2, "read disc info failed\n");
+ return n;
+ }
+ if (vflag)
+ fprint(2, "read disc info succeeded\n");
+ assert((resp[2] & 0340) == 0); /* data type 0 */
+ drive->erasable = ((resp[2] & 0x10) != 0); /* -RW? */
+ drive->erasableset = 1;
+ return n;
+}
+
+static int
+getdvdstruct(Drive *drive)
+{
+ int n, cat;
+ uchar cmd[12], resp[Pagesz];
+
+ initcdb(cmd, sizeof cmd, ScmdReadDVD); /* actually, read disc structure */
+ cmd[1] = 0; /* media type: dvd */
+ cmd[7] = 0; /* format code: physical format */
+ cmd[8] = sizeof resp >> 8; /* allocation length */
+ cmd[9] = sizeof resp;
+ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread);
+ if (n < 7)
+ return -1;
+
+// print("dvd structure:\n");
+// hexdump(resp, n);
+
+ /* resp[0..1] is resp length */
+ cat = (resp[4] & 0xf0) >> 4; /* disk category, MMC-6 §6.22.3.2.1 */
+ if (vflag)
+ fprint(2, "dvd type is %s\n", dvdtype[cat]);
+ drive->dvdtype = dvdtype[cat];
+ /* write parameters mode page may suffice to compute writeok for dvd */
+ drive->erasable = drive->recordable = 0;
+ /*
+ * the layer-type field is a *bit array*,
+ * though an enumeration of types would make more sense,
+ * since the types are exclusive, not orthogonal.
+ */
+ if (resp[6] & (1<<2)) /* rewritable? */
+ drive->erasable = 1;
+ else if (resp[6] & (1<<1)) /* recordable once? */
+ drive->recordable = 1;
+ else { /* factory-pressed disk */
+ drive->blank = 0;
+ drive->blankset = 1;
+ }
+ drive->erasableset = drive->recordableset = 1;
+ drive->mmctype = (cat >= 8? Mmcdvdplus: Mmcdvdminus);
+ return 0;
+}
+
+static int
+getbdstruct(Drive *drive)
+{
+ int n;
+ uchar cmd[12], resp[4100];
+
+ initcdb(cmd, sizeof cmd, ScmdReadDVD); /* actually, read disc structure */
+ cmd[1] = 1; /* media type: bd */
+ cmd[7] = 0; /* format code: disc info */
+ cmd[8] = sizeof resp >> 8; /* allocation length */
+ cmd[9] = sizeof resp;
+ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread);
+ /*
+ * resp[0..1] is resp length.
+ * resp[4+8..4+8+2] is bd type (disc type identifier):
+ * BDO|BDW|BDR, MMC-6 §6.22.3.3.1. The above command should
+ * fail on DVD drives, but some seem to ignore media type
+ * and return successfully, so verify that it's a BD drive.
+ */
+ if (n < 4+8+3 || resp[4+8] != 'B' || resp[4+8+1] != 'D')
+ return -1;
+ if (vflag)
+ fprint(2, "read disc structure (bd) succeeded\n");
+ drive->erasable = drive->recordable = 0;
+ switch (resp[4+8+2]) {
+ case 'O':
+ drive->blank = 0;
+ drive->blankset = 1;
+ break;
+ case 'R': /* Recordable */
+ drive->recordable = 1;
+ break;
+ case 'W': /* reWritable */
+ drive->erasable = 1;
+ break;
+ default:
+ fprint(2, "%s: unknown bd type BD%c\n", argv0, resp[4+8+2]);
+ return -1;
+ }
+ drive->erasableset = drive->recordableset = 1;
+ drive->mmctype = Mmcbd;
+ return 0;
+}
+
+/*
+ * infer endings from the beginnings of other tracks.
+ */
+static void
+mmcinfertracks(Drive *drive, int first, int last)
+{
+ int i;
+ uchar resp[1024];
+ ulong tot;
+ Track *t;
+
+ if (vflag)
+ fprint(2, "inferring tracks\n");
+ for(i = first; i <= last; i++) {
+ memset(resp, 0, sizeof(resp));
+ if(mmcreadtoc(drive, 0, i, resp, sizeof(resp)) < 0)
+ break;
+ t = &drive->track[i-first];
+ t->mtime = drive->changetime;
+ t->type = TypeData;
+ t->bs = BScdrom;
+ t->beg = bige(resp+8);
+ if(!(resp[5] & 4)) {
+ t->type = TypeAudio;
+ t->bs = BScdda;
+ }
+ }
+
+ if((long)drive->track[0].beg < 0) /* i've seen negative track 0's */
+ drive->track[0].beg = 0;
+
+ tot = 0;
+ memset(resp, 0, sizeof(resp));
+ /* 0xAA is lead-out */
+ if(mmcreadtoc(drive, 0, 0xAA, resp, sizeof(resp)) < 0)
+ print("bad\n");
+ if(resp[6])
+ tot = bige(resp+8);
+
+ for(i=last; i>=first; i--) {
+ t = &drive->track[i-first];
+ t->end = tot;
+ tot = t->beg;
+ if(t->end <= t->beg)
+ t->beg = t->end = 0;
+ /* -2: skip lead out */
+ t->size = (t->end - t->beg - 2) * (vlong)t->bs;
+ }
+}
+
+/* this gets called a lot from main.c's 9P routines */
+static int
+mmcgettoc(Drive *drive)
+{
+ int i, n, first, last;
+ uchar resp[1024];
+ Mmcaux *aux;
+
+ /*
+ * if someone has swapped the cd,
+ * mmcreadtoc will get ``medium changed'' and the
+ * scsi routines will set nchange and changetime in the
+ * scsi device.
+ */
+ mmcreadtoc(drive, 0, 0, resp, sizeof(resp));
+ if(drive->Scsi.changetime == 0) { /* no media present */
+ drive->mmctype = Mmcnone;
+ drive->ntrack = 0;
+ return 0;
+ }
+ /*
+ * if the disc doesn't appear to be have been changed, and there
+ * is a disc in this drive, there's nothing to do (the common case).
+ */
+ if(drive->nchange == drive->Scsi.nchange && drive->changetime != 0)
+ return 0;
+
+ /*
+ * the disc in the drive may have just been changed,
+ * so rescan it and relearn all about it.
+ */
+
+ drive->ntrack = 0;
+ drive->nameok = 0;
+ drive->nchange = drive->Scsi.nchange;
+ drive->changetime = drive->Scsi.changetime;
+ drive->writeok = drive->erasable = drive->recordable = drive->blank = 0;
+ drive->erasableset = drive->recordableset = drive->blankset = 0;
+ aux = drive->aux;
+ aux->mmcnwa = 0;
+ aux->nropen = aux->nwopen = 0;
+ aux->ntotby = aux->ntotbk = 0;
+
+ for(i=0; i<nelem(drive->track); i++){
+ memset(&drive->track[i].mbeg, 0, sizeof(Msf));
+ memset(&drive->track[i].mend, 0, sizeof(Msf));
+ }
+
+ /*
+ * TODO: set read ahead, MMC-6 §6.37, seems to control caching.
+ */
+
+ /*
+ * find number of tracks
+ */
+ if((n = mmcreadtoc(drive, Msfbit, 0, resp, sizeof(resp))) < 4) {
+ /*
+ * on a blank disc in a cd-rw, use readdiscinfo
+ * to find the track info.
+ */
+ if(getdiscinfo(drive, resp, sizeof(resp)) < 7)
+ return -1;
+ assert((resp[2] & 0340) == 0); /* data type 0 */
+ if(resp[4] != 1)
+ print("multi-session disc %d\n", resp[4]);
+ first = resp[3];
+ last = resp[6];
+ if(vflag)
+ print("blank disc %d %d\n", first, last);
+ /* the assumption of blankness may be unwarranted */
+ drive->writeok = drive->blank = drive->blankset = 1;
+ } else {
+ first = resp[2];
+ last = resp[3];
+
+ if(n >= 4+8*(last-first+2)) {
+ /* resp[4 + i*8 + 2] is track # */
+ /* <=: track[last-first+1] = end */
+ for(i=0; i<=last-first+1; i++)
+ drive->track[i].mbeg = rdmsf(resp+4+i*8+5);
+ for(i=0; i<last-first+1; i++)
+ drive->track[i].mend = drive->track[i+1].mbeg;
+ }
+ }
+
+ drive->mmctype = Mmcnone;
+ drive->dvdtype = nil;
+ getdvdstruct(drive);
+ getbdstruct(drive);
+ if (drive->mmctype == Mmcnone)
+ drive->mmctype = Mmccd; /* by default */
+ if (drive->recordable || drive->erasable)
+ drive->writeok = 1;
+
+ if (vflag) {
+ fprint(2, "writeok %d", drive->writeok);
+ /* drive->blank is never used and hard to figure out */
+// if (drive->blankset)
+// fprint(2, " blank %d", drive->blank);
+ if (drive->recordableset)
+ fprint(2, " recordable %d", drive->recordable);
+ if (drive->erasableset)
+ fprint(2, " erasable %d", drive->erasable);
+ fprint(2, "\n");
+ print("first %d last %d\n", first, last);
+ }
+
+ if(first == 0 && last == 0)
+ first = 1;
+
+ if(first <= 0 || first >= Maxtrack) {
+ werrstr("first table %d not in range", first);
+ return -1;
+ }
+ if(last <= 0 || last >= Maxtrack) {
+ werrstr("last table %d not in range", last);
+ return -1;
+ }
+
+ if(drive->cap & Cwrite) /* CDR drives are easy */
+ for(i = first; i <= last; i++)
+ mmctrackinfo(drive, i, i - first);
+ else
+ /*
+ * otherwise we need to infer endings from the
+ * beginnings of other tracks.
+ */
+ mmcinfertracks(drive, first, last);
+
+ drive->firsttrack = first;
+ drive->ntrack = last+1-first;
+ return 0;
+}
+
+static void
+settrkmode(uchar *p, int mode)
+{
+ p[Wptrkmode] &= ~0xf;
+ p[Wptrkmode] |= mode;
+}
+
+/*
+ * this uses page 5, which is optional.
+ */
+static int
+mmcsetbs(Drive *drive, int bs)
+{
+ uchar *p;
+ Mmcaux *aux;
+
+ aux = drive->aux;
+ if (!aux->page05ok)
+ return 0; /* harmless; assume 2k */
+
+ p = aux->page05;
+ /*
+ * establish defaults.
+ */
+ p[0] &= 077; /* clear reserved bits, MMC-6 §7.2.3 */
+ p[Wpwrtype] = Bufe | Wttrackonce;
+// if(testonlyflag)
+// p[Wpwrtype] |= 0x10; /* test-write */
+ /* assume dvd values as defaults */
+ settrkmode(p, Tmintr);
+ p[Wptrkmode] |= Msnext;
+ p[Wpdatblktype] = Db2kdata;
+ p[Wpsessfmt] = Sfdata;
+
+ switch(drive->mmctype) {
+ case Mmcdvdplus:
+ case Mmcbd:
+ break;
+ case Mmcdvdminus:
+ /* dvd-r can only do disc-at-once or incremental */
+ p[Wpwrtype] = Bufe | Wtsessonce;
+ break;
+ case Mmccd:
+ settrkmode(p, Tmunintr); /* data track, uninterrupted */
+ switch(bs){
+ case BScdda:
+ /* 2 audio channels without pre-emphasis */
+ settrkmode(p, Tmcdda); /* TODO: should be Tm2audio? */
+ p[Wpdatblktype] = Dbraw;
+ break;
+ case BScdrom:
+ break;
+ case BScdxa:
+ p[Wpdatblktype] = Db2336;
+ p[Wpsessfmt] = Sfcdxa;
+ break;
+ default:
+ fprint(2, "%s: unknown CD type; bs %d\n", argv0, bs);
+ assert(0);
+ }
+ break;
+ default:
+ fprint(2, "%s: unknown disc sub-type %d\n",
+ argv0, drive->mmctype);
+ break;
+ }
+ if(mmcsetpage(drive, Pagwrparams, p) < 0) {
+ if (vflag)
+ fprint(2, "mmcsetbs: could NOT set write parameters page\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* off is a block number */
+static long
+mmcread(Buf *buf, void *v, long nblock, ulong off)
+{
+ int bs;
+ long n, nn;
+ uchar cmd[12];
+ Drive *drive;
+ Otrack *o;
+
+ o = buf->otrack;
+ drive = o->drive;
+ bs = o->track->bs;
+ off += o->track->beg;
+
+ if(nblock >= (1<<10)) {
+ werrstr("mmcread too big");
+ if(vflag)
+ fprint(2, "mmcread too big\n");
+ return -1;
+ }
+
+ /* truncate nblock modulo size of track */
+ if(off > o->track->end - 2) {
+ werrstr("read past end of track");
+ if(vflag)
+ fprint(2, "end of track (%ld->%ld off %ld)",
+ o->track->beg, o->track->end - 2, off);
+ return -1;
+ }
+ if(off == o->track->end - 2)
+ return 0;
+
+ if(off+nblock > o->track->end - 2)
+ nblock = o->track->end - 2 - off;
+
+ /*
+ * `read cd' only works for CDs; for everybody else,
+ * we'll try plain `read (12)'. only use read cd if it's
+ * a cd drive with a cd in it and we're not reading data
+ * (e.g., reading audio).
+ */
+ memset(cmd, 0, sizeof(cmd));
+ if (drive->type == TypeCD && drive->mmctype == Mmccd && bs != BScdrom) {
+ cmd[0] = ScmdReadcd;
+ cmd[2] = off>>24;
+ cmd[3] = off>>16;
+ cmd[4] = off>>8;
+ cmd[5] = off>>0;
+ cmd[6] = nblock>>16;
+ cmd[7] = nblock>>8;
+ cmd[8] = nblock>>0;
+ cmd[9] = 0x10;
+ switch(bs){
+ case BScdda:
+ cmd[1] = 0x04;
+ break;
+ case BScdrom:
+ cmd[1] = 0x08;
+ break;
+ case BScdxa:
+ cmd[1] = 0x0C;
+ break;
+ default:
+ werrstr("unknown bs %d", bs);
+ return -1;
+ }
+ } else { /* e.g., TypeDA */
+ cmd[0] = ScmdRead12;
+ cmd[2] = off>>24;
+ cmd[3] = off>>16;
+ cmd[4] = off>>8;
+ cmd[5] = off>>0;
+ cmd[6] = nblock>>24;
+ cmd[7] = nblock>>16;
+ cmd[8] = nblock>>8;
+ cmd[9] = nblock;
+ // cmd[10] = 0x80; /* streaming */
+ }
+ n = nblock*bs;
+ nn = scsi(drive, cmd, sizeof(cmd), v, n, Sread);
+ if(nn != n) {
+ if(nn != -1)
+ werrstr("short read %ld/%ld", nn, n);
+ if(vflag)
+ print("read off %lud nblock %ld bs %d failed\n",
+ off, nblock, bs);
+ return -1;
+ }
+
+ return nblock;
+}
+
+static Otrack*
+mmcopenrd(Drive *drive, int trackno)
+{
+ Otrack *o;
+ Mmcaux *aux;
+
+ if(trackno < 0 || trackno >= drive->ntrack) {
+ werrstr("track number out of range");
+ return nil;
+ }
+
+ aux = drive->aux;
+ if(aux->nwopen) {
+ werrstr("disk in use for writing");
+ return nil;
+ }
+
+ o = emalloc(sizeof(Otrack));
+ o->drive = drive;
+ o->track = &drive->track[trackno];
+ o->nchange = drive->nchange;
+ o->omode = OREAD;
+ o->buf = bopen(mmcread, OREAD, o->track->bs, Readblock);
+ o->buf->otrack = o;
+
+ aux->nropen++;
+
+ return o;
+}
+
+static int
+format(Drive *drive)
+{
+ ulong nblks, blksize;
+ uchar *fmtdesc;
+ uchar cmd[6], parms[4+8];
+
+ if (drive->recordable && drive->mmctype != Mmcbd) {
+ werrstr("don't format write-once cd or dvd media");
+ return -1;
+ }
+ initcdb(cmd, sizeof cmd, ScmdFormat); /* format unit */
+ cmd[1] = 0x10 | 1; /* format data, mmc format code */
+
+ memset(parms, 0, sizeof parms);
+ /* format list header */
+ parms[1] = 2; /* immediate return; don't wait */
+ parms[3] = 8; /* format descriptor length */
+
+ fmtdesc = parms + 4;
+ fmtdesc[4] = 0; /* full format */
+
+ nblks = 0;
+ blksize = BScdrom;
+ switch (drive->mmctype) {
+ case Mmccd:
+ nblks = 0;
+ break;
+ case Mmcdvdplus:
+ /* format type 0 is optional but equiv to format type 0x26 */
+ fmtdesc[4] = 0x26 << 2;
+ nblks = ~0ul;
+ blksize = 0;
+ break;
+ }
+
+ PUTBELONG(fmtdesc, nblks);
+ PUTBE24(fmtdesc + 5, blksize);
+
+// print("format parameters:\n");
+// hexdump(parms, sizeof parms);
+
+ if(vflag)
+ print("%lld ns: format\n", nsec());
+ return scsi(drive, cmd, sizeof(cmd), parms, sizeof parms, Swrite);
+}
+
+static long
+mmcxwrite(Otrack *o, void *v, long nblk)
+{
+ uchar cmd[10];
+ Mmcaux *aux;
+
+ assert(o->omode == OWRITE);
+ aux = o->drive->aux;
+ if (aux->mmcnwa == 0 && scsiready(o->drive) < 0) {
+ werrstr("device not ready to write");
+ return -1;
+ }
+ aux->ntotby += nblk*o->track->bs;
+ aux->ntotbk += nblk;
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdExtwrite; /* write (10) */
+ cmd[2] = aux->mmcnwa>>24;
+ cmd[3] = aux->mmcnwa>>16;
+ cmd[4] = aux->mmcnwa>>8;
+ cmd[5] = aux->mmcnwa;
+ cmd[7] = nblk>>8;
+ cmd[8] = nblk>>0;
+ if(vflag)
+ print("%lld ns: write %ld at 0x%lux\n",
+ nsec(), nblk, aux->mmcnwa);
+ aux->mmcnwa += nblk;
+ return scsi(o->drive, cmd, sizeof(cmd), v, nblk*o->track->bs, Swrite);
+}
+
+static long
+mmcwrite(Buf *buf, void *v, long nblk, ulong)
+{
+ return mmcxwrite(buf->otrack, v, nblk);
+}
+
+enum {
+ Eccblk = 128, /* sectors per ecc block */
+ Rsvslop = 0,
+};
+
+static int
+reserve(Drive *drive, int track)
+{
+ ulong sz;
+ uchar cmd[10];
+
+ initcdb(cmd, sizeof cmd, ScmdReserve);
+ track -= drive->firsttrack; /* switch to zero-origin */
+ if (track >= 0 && track < drive->ntrack)
+ /* .end is next sector past sz */
+ sz = drive->track[track].end - drive->track[track].beg - Rsvslop;
+ else {
+ sz = Eccblk;
+ fprint(2, "%s: reserve: track #%d out of range 0-%d\n",
+ argv0, track, drive->ntrack);
+ }
+ sz -= sz % Eccblk; /* round down to ecc-block multiple */
+ if ((long)sz < 0) {
+ fprint(2, "%s: reserve: bogus size %lud\n", argv0, sz);
+ return -1;
+ }
+ cmd[1] = 0; /* no ASRV: allocate by size not lba */
+ PUTBELONG(cmd + 2 + 3, sz);
+ if (vflag)
+ fprint(2, "reserving %ld sectors\n", sz);
+ return scsi(drive, cmd, sizeof cmd, cmd, 0, Snone);
+}
+
+static int
+getinvistrack(Drive *drive)
+{
+ int n;
+ uchar cmd[10], resp[Pagesz];
+
+ initcdb(cmd, sizeof(cmd), ScmdRtrackinfo);
+ cmd[1] = 1<<2 | 1; /* open; address below is logical track # */
+ PUTBELONG(cmd + 2, 1); /* find first open track */
+ cmd[7] = sizeof(resp)>>8;
+ cmd[8] = sizeof(resp);
+ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
+ if(n < 4) {
+ if(vflag)
+ print("trackinfo for invis track fails n=%d: %r\n", n);
+ return -1;
+ }
+
+ if(vflag)
+ print("getinvistrack: track #%d session #%d\n",
+ resp[2], resp[3]);
+ return resp[2];
+}
+
+static Otrack*
+mmccreate(Drive *drive, int type)
+{
+ int bs, invis;
+ Mmcaux *aux;
+ Track *t;
+ Otrack *o;
+
+ aux = drive->aux;
+
+ if(aux->nropen || aux->nwopen) {
+ werrstr("drive in use");
+ return nil;
+ }
+
+ switch(type){
+ case TypeAudio:
+ bs = BScdda;
+ break;
+ case TypeData:
+ bs = BScdrom;
+ break;
+ default:
+ werrstr("bad type %d", type);
+ return nil;
+ }
+
+ invis = getinvistrack(drive);
+ if (invis < 0)
+ invis = Invistrack;
+
+ /* comment out the returns for now; it should be no big deal - geoff */
+ if(mmctrackinfo(drive, invis, Maxtrack)) {
+ if (vflag)
+ fprint(2, "mmccreate: mmctrackinfo for invis track %d"
+ " failed: %r\n", invis);
+ werrstr("disc not writable");
+// return nil;
+ }
+ if(mmcsetbs(drive, bs) < 0) {
+ werrstr("cannot set bs mode");
+// return nil;
+ }
+ if(mmctrackinfo(drive, invis, Maxtrack)) {
+ if (vflag)
+ fprint(2, "mmccreate: mmctrackinfo for invis track %d"
+ " (2) failed: %r\n", invis);
+ werrstr("disc not writable 2");
+// return nil;
+ }
+
+ /* special hack for dvd-r: reserve the invisible track */
+ if (drive->mmctype == Mmcdvdminus && drive->writeok &&
+ drive->recordable && reserve(drive, invis) < 0) {
+ if (vflag)
+ fprint(2, "mmcreate: reserving track %d for dvd-r "
+ "failed: %r\n", invis);
+ return nil;
+ }
+
+ aux->ntotby = 0;
+ aux->ntotbk = 0;
+
+ t = &drive->track[drive->ntrack++];
+ t->size = 0;
+ t->bs = bs;
+ t->beg = aux->mmcnwa;
+ t->end = 0;
+ t->type = type;
+ drive->nameok = 0;
+
+ o = emalloc(sizeof(Otrack));
+ o->drive = drive;
+ o->nchange = drive->nchange;
+ o->omode = OWRITE;
+ o->track = t;
+ o->buf = bopen(mmcwrite, OWRITE, bs, Readblock);
+ o->buf->otrack = o;
+
+ aux->nwopen++;
+
+ if(vflag)
+ print("mmcinit: nwa = 0x%luX\n", aux->mmcnwa);
+ return o;
+}
+
+/*
+ * issue some form of close track, close session or finalize disc command.
+ * see The Matrix, table 252 in MMC-6 §6.3.2.3.
+ */
+static int
+mmcxclose(Drive *drive, int clf, int trackno)
+{
+ uchar cmd[10];
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdClosetracksess;
+ /* cmd[1] & 1 is the immediate bit */
+ cmd[2] = clf; /* close function */
+ if(clf == Closetrack)
+ cmd[5] = trackno;
+ return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
+}
+
+/* flush drive cache, close current track */
+void
+mmcsynccache(Drive *drive)
+{
+ int invis;
+ uchar cmd[10];
+ Mmcaux *aux;
+
+ initcdb(cmd, sizeof cmd, ScmdSynccache);
+ /*
+ * this will take a long time to burn the remainder of a dvd-r
+ * with a reserved track covering the whole disc.
+ */
+ if (vflag) {
+ fprint(2, "syncing cache");
+ if (drive->mmctype == Mmcdvdminus && drive->writeok &&
+ drive->recordable)
+ fprint(2, "; dvd-r burning rest of track reservation, "
+ "will be slow");
+ fprint(2, "\n");
+ }
+ scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
+ if(vflag) {
+ aux = drive->aux;
+ print("mmcsynccache: bytes = %lld blocks = %ld, mmcnwa 0x%luX\n",
+ aux->ntotby, aux->ntotbk, aux->mmcnwa);
+ }
+
+ invis = getinvistrack(drive);
+ if (invis < 0)
+ invis = Invistrack;
+ /*
+ * rsc: seems not to work on some drives.
+ * so ignore return code & don't issue on dvd+rw.
+ */
+ if(drive->mmctype != Mmcdvdplus || !drive->erasable) {
+ if (vflag)
+ fprint(2, "closing invisible track %d (not dvd+rw)...\n",
+ invis);
+ mmcxclose(drive, Closetrack, invis);
+ if (vflag)
+ fprint(2, "... done.\n");
+ }
+}
+
+/*
+ * close the open track `o'.
+ */
+static void
+mmcclose(Otrack *o)
+{
+ Mmcaux *aux;
+ static uchar zero[2*BSmax];
+
+ aux = o->drive->aux;
+ if(o->omode == OREAD)
+ aux->nropen--;
+ else if(o->omode == OWRITE) {
+ aux->nwopen--;
+ mmcxwrite(o, zero, 2); /* write lead out */
+ mmcsynccache(o->drive);
+ o->drive->nchange = -1; /* force reread toc */
+ }
+ free(o);
+}
+
+static int
+setonesess(Drive *drive)
+{
+ uchar *p;
+ Mmcaux *aux;
+
+ /* page 5 is legacy and now read-only; see MMC-6 §7.5.4.1 */
+ aux = drive->aux;
+ p = aux->page05;
+ p[Wptrkmode] &= ~Msbits;
+ p[Wptrkmode] |= Msnonext;
+ return mmcsetpage(drive, Pagwrparams, p);
+}
+
+/*
+ * close the current session, then finalize the disc.
+ */
+static int
+mmcfixate(Drive *drive)
+{
+ int r;
+
+ if((drive->cap & Cwrite) == 0) {
+ werrstr("not a writer");
+ return -1;
+ }
+ drive->nchange = -1; /* force reread toc */
+
+ setonesess(drive);
+
+ /* skip explicit close session on bd-r */
+ if (drive->mmctype != Mmcbd || drive->erasable) {
+ if (vflag)
+ fprint(2, "closing session and maybe finalizing...\n");
+ r = mmcxclose(drive, Closesessfinal, 0);
+ if (vflag)
+ fprint(2, "... done.\n");
+ if (r < 0)
+ return r;
+ }
+ /*
+ * Closesessfinal only closes & doesn't finalize on dvd+r and bd-r.
+ * Closedvdrbdfinal closes & finalizes dvd+r and bd-r.
+ */
+ if ((drive->mmctype == Mmcdvdplus || drive->mmctype == Mmcbd) &&
+ !drive->erasable) {
+ if (vflag)
+ fprint(2, "finalizing dvd+r or bd-r... "
+ "(won't print `done').\n");
+ return mmcxclose(drive, Closedvdrbdfinal, 0);
+ }
+ return 0;
+}
+
+static int
+mmcblank(Drive *drive, int quick)
+{
+ uchar cmd[12];
+
+ drive->nchange = -1; /* force reread toc */
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdBlank; /* blank cd-rw media */
+ /* immediate bit is 0x10 */
+ /* cmd[1] = 0 means blank the whole disc; = 1 just the header */
+ cmd[1] = quick ? 0x01 : 0x00;
+
+ return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
+}
+
+static char*
+e(int status)
+{
+ if(status < 0)
+ return geterrstr();
+ return nil;
+}
+
+static char*
+mmcctl(Drive *drive, int argc, char **argv)
+{
+ char *cmd;
+
+ if(argc < 1)
+ return nil;
+ cmd = argv[0];
+ if(strcmp(cmd, "format") == 0)
+ return e(format(drive));
+ if(strcmp(cmd, "blank") == 0)
+ return e(mmcblank(drive, 0));
+ if(strcmp(cmd, "quickblank") == 0)
+ return e(mmcblank(drive, 1));
+ if(strcmp(cmd, "eject") == 0)
+ return e(start(drive, 2));
+ if(strcmp(cmd, "ingest") == 0)
+ return e(start(drive, 3));
+ return "bad arg";
+}
+
+static char*
+mmcsetspeed(Drive *drive, int r, int w)
+{
+ char *rv;
+ uchar cmd[12];
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = ScmdSetcdspeed;
+ cmd[2] = r>>8;
+ cmd[3] = r;
+ cmd[4] = w>>8;
+ cmd[5] = w;
+ rv = e(scsi(drive, cmd, sizeof(cmd), nil, 0, Snone));
+ mmcgetspeed(drive);
+ return rv;
+}
+
+static Dev mmcdev = {
+ mmcopenrd,
+ mmccreate,
+ bufread,
+ bufwrite,
+ mmcclose,
+ mmcgettoc,
+ mmcfixate,
+ mmcctl,
+ mmcsetspeed,
+};