xref: /plan9-contrib/sys/src/cmd/cdfs/mmc.c (revision b94bb474148e9d24a82a427863d9c9eb4c20f4ae)
1 /*
2  * multi-media commands
3  *
4  * as of mmc-6, mode page 0x2a (capabilities & mechanical status) is legacy
5  * and read-only, last defined in mmc-3.  mode page 5 (write parameters)
6  * applies only to cd-r(w) and dvd-r(w); *-rom, dvd+* and bd-* are right out.
7  */
8 #include <u.h>
9 #include <libc.h>
10 #include <disk.h>
11 #include "../scuzz/scsireq.h"
12 #include "dat.h"
13 #include "fns.h"
14 
15 enum
16 {
17 	Desperate	= 0,	/* non-zero grubs around in inquiry string */
18 
19 	Pagesz		= 255,
20 
21 	Pagwrparams	= 5,	/* (cd|dvd)-r(w) device write parameters */
22 	Pagcache	= 8,
23 	Pagcapmechsts	= 0x2a,
24 
25 	Invistrack	= 0xff,	/* the invisible & incomplete track */
26 };
27 
28 static Dev mmcdev;
29 
30 typedef struct Mmcaux Mmcaux;
31 struct Mmcaux {
32 	/* drive characteristics */
33 	uchar	page05[Pagesz];		/* write parameters */
34 	int	page05ok;
35 	int	pagecmdsz;
36 
37 	/* disc characteristics */
38 	long	mmcnwa;			/* next writable address (block #) */
39 	int	nropen;
40 	int	nwopen;
41 	vlong	ntotby;
42 	long	ntotbk;
43 };
44 
45 /* these will be printed as user ids, so no spaces please */
46 static char *dvdtype[] = {
47 	"dvd-rom",
48 	"dvd-ram",
49 	"dvd-r",
50 	"dvd-rw",
51 	"hd-dvd-rom",
52 	"hd-dvd-ram",
53 	"hd-dvd-r",
54 	"type-7-unknown",
55 	"type-8-unknown",
56 	"dvd+rw",
57 	"dvd+r",
58 	"type-11-unknown",
59 	"type-12-unknown",
60 	"dvd+rw-dl",
61 	"dvd+r-dl",
62 	"type-15-unknown",
63 };
64 
65 static int getinvistrack(Drive *drive);
66 
67 static ulong
68 bige(void *p)
69 {
70 	uchar *a;
71 
72 	a = p;
73 	return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
74 }
75 
76 static ushort
77 biges(void *p)
78 {
79 	uchar *a;
80 
81 	a = p;
82 	return (a[0]<<8) | a[1];
83 }
84 
85 ulong
86 getnwa(Drive *drive)
87 {
88 	Mmcaux *aux;
89 
90 	aux = drive->aux;
91 	return aux->mmcnwa;
92 }
93 
94 static void
95 hexdump(void *v, int n)
96 {
97 	int i;
98 	uchar *p;
99 
100 	p = v;
101 	for(i=0; i<n; i++){
102 		print("%.2ux ", p[i]);
103 		if((i%8) == 7)
104 			print("\n");
105 	}
106 	if(i%8)
107 		print("\n");
108 }
109 
110 static void
111 initcdb(uchar *cdb, int len, int cmd)
112 {
113 	memset(cdb, 0, len);
114 	cdb[0] = cmd;
115 }
116 
117 /*
118  * SCSI CDBs (cmd arrays) are 6, 10, 12, 16 or 32 bytes long.
119  * The mode sense/select commands implicitly refer to
120  * a mode parameter list, which consists of an 8-byte
121  * mode parameter header, followed by zero or more block
122  * descriptors and zero or more mode pages (MMC-2 §5.5.2).
123  * We'll ignore mode sub-pages.
124  * Integers are stored big-endian.
125  *
126  * The format of the mode parameter (10) header is:
127  *	ushort	mode_data_length;		// of following bytes
128  *	uchar	medium_type;
129  *	uchar	device_specific;
130  *	uchar	reserved[2];
131  *	ushort	block_descriptor_length;	// zero
132  *
133  * The format of the mode parameter (6) header is:
134  *	uchar	mode_data_length;		// of following bytes
135  *	uchar	medium_type;
136  *	uchar	device_specific;
137  *	uchar	block_descriptor_length;	// zero
138  *
139  * The format of the mode pages is:
140  *	uchar	page_code_and_PS;
141  *	uchar	page_len;			// of following bytes
142  *	uchar	parameter[page_len];
143  *
144  * see SPC-3 §4.3.4.6 for allocation length and §7.4 for mode parameter lists.
145  */
146 
147 enum {
148 	Mode10parmhdrlen= 8,
149 	Mode6parmhdrlen	= 4,
150 	Modepaghdrlen	= 2,
151 };
152 
153 static int
154 mmcgetpage10(Drive *drive, int page, void *v)
155 {
156 	uchar cmd[10], resp[512];
157 	int n, r;
158 
159 	initcdb(cmd, sizeof cmd, ScmdMsense10);
160 	cmd[2] = page;
161 	cmd[8] = 255;			/* allocation length: buffer size */
162 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
163 	if(n < Mode10parmhdrlen)
164 		return -1;
165 
166 	r = (resp[6]<<8) | resp[7];	/* block descriptor length */
167 	n -= Mode10parmhdrlen + r;
168 
169 	if(n < 0)
170 		return -1;
171 	if(n > Pagesz)
172 		n = Pagesz;
173 
174 	memmove(v, &resp[Mode10parmhdrlen + r], n);
175 	return n;
176 }
177 
178 static int
179 mmcgetpage6(Drive *drive, int page, void *v)
180 {
181 	uchar cmd[6], resp[512];
182 	int n;
183 
184 	initcdb(cmd, sizeof cmd, ScmdMsense6);
185 	cmd[2] = page;
186 	cmd[4] = 255;			/* allocation length */
187 
188 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
189 	if(n < Mode6parmhdrlen)
190 		return -1;
191 
192 	n -= Mode6parmhdrlen + resp[3];
193 	if(n < 0)
194 		return -1;
195 	if(n > Pagesz)
196 		n = Pagesz;
197 
198 	memmove(v, &resp[Mode6parmhdrlen + resp[3]], n);
199 	return n;
200 }
201 
202 static int
203 mmcsetpage10(Drive *drive, int page, void *v)
204 {
205 	uchar cmd[10], *p, *pagedata;
206 	int len, n;
207 
208 	/* allocate parameter list, copy in mode page, fill in header */
209 	pagedata = v;
210 	assert(pagedata[0] == page);
211 	len = Mode10parmhdrlen + Modepaghdrlen + pagedata[1];
212 	p = emalloc(len);
213 	memmove(p + Mode10parmhdrlen, pagedata, pagedata[1]);
214 	/* parameter list header */
215 	p[0] = 0;
216 	p[1] = len - 2;
217 
218 	/* set up CDB */
219 	initcdb(cmd, sizeof cmd, ScmdMselect10);
220 	cmd[1] = 0x10;			/* format not vendor-specific */
221 	cmd[8] = len;
222 
223 //	print("set: sending cmd\n");
224 //	hexdump(cmd, 10);
225 //	print("parameter list header\n");
226 //	hexdump(p, Mode10parmhdrlen);
227 //	print("page\n");
228 //	hexdump(p + Mode10parmhdrlen, len - Mode10parmhdrlen);
229 
230 	n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite);
231 
232 //	print("set: got cmd\n");
233 //	hexdump(cmd, 10);
234 
235 	free(p);
236 	if(n < len)
237 		return -1;
238 	return 0;
239 }
240 
241 static int
242 mmcsetpage6(Drive *drive, int page, void *v)
243 {
244 	uchar cmd[6], *p, *pagedata;
245 	int len, n;
246 
247 	if (vflag)
248 		print("mmcsetpage6 called!\n");
249 	pagedata = v;
250 	assert(pagedata[0] == page);
251 	len = Mode6parmhdrlen + Modepaghdrlen + pagedata[1];
252 	p = emalloc(len);
253 	memmove(p + Mode6parmhdrlen, pagedata, pagedata[1]);
254 
255 	initcdb(cmd, sizeof cmd, ScmdMselect6);
256 	cmd[1] = 0x10;			/* format not vendor-specific */
257 	cmd[4] = len;
258 
259 	n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite);
260 	free(p);
261 	if(n < len)
262 		return -1;
263 	return 0;
264 }
265 
266 static int
267 mmcgetpage(Drive *drive, int page, void *v)
268 {
269 	Mmcaux *aux;
270 
271 	aux = drive->aux;
272 	switch(aux->pagecmdsz) {
273 	case 10:
274 		return mmcgetpage10(drive, page, v);
275 	case 6:
276 		return mmcgetpage6(drive, page, v);
277 	default:
278 		assert(0);
279 	}
280 	return -1;
281 }
282 
283 static int
284 mmcsetpage(Drive *drive, int page, void *v)
285 {
286 	Mmcaux *aux;
287 
288 	aux = drive->aux;
289 	switch(aux->pagecmdsz) {
290 	case 10:
291 		return mmcsetpage10(drive, page, v);
292 	case 6:
293 		return mmcsetpage6(drive, page, v);
294 	default:
295 		assert(0);
296 	}
297 	return -1;
298 }
299 
300 int
301 mmcstatus(Drive *drive)
302 {
303 	uchar cmd[12];
304 
305 	initcdb(cmd, sizeof cmd, ScmdCDstatus);		/* mechanism status */
306 	return scsi(drive, cmd, sizeof(cmd), nil, 0, Sread);
307 }
308 
309 void
310 mmcgetspeed(Drive *drive)
311 {
312 	int n, maxread, curread, maxwrite, curwrite;
313 	uchar buf[Pagesz];
314 
315 	memset(buf, 0, 22);
316 	n = mmcgetpage(drive, Pagcapmechsts, buf);	/* legacy page */
317 	if (n < 22) {
318 		if (vflag)
319 			fprint(2, "no Pagcapmechsts mode page!\n");
320 		return;
321 	}
322 	maxread =   (buf[8]<<8)|buf[9];
323 	curread =  (buf[14]<<8)|buf[15];
324 	maxwrite = (buf[18]<<8)|buf[19];
325 	curwrite = (buf[20]<<8)|buf[21];
326 
327 	if(maxread && maxread < 170 || curread && curread < 170)
328 		return;			/* bogus data */
329 
330 	drive->readspeed = curread;
331 	drive->writespeed = curwrite;
332 	drive->maxreadspeed = maxread;
333 	drive->maxwritespeed = maxwrite;
334 }
335 
336 static int
337 getdevtype(Drive *drive)
338 {
339 	int n;
340 	uchar cmd[6], resp[Pagesz];
341 
342 	initcdb(cmd, sizeof cmd, ScmdInq);
343 	cmd[3] = sizeof resp >> 8;
344 	cmd[4] = sizeof resp;
345 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread);
346 	if (n < 8)
347 		return -1;
348 	return resp[0] & 037;
349 }
350 
351 static int
352 start(Drive *drive, int code)
353 {
354 	uchar cmd[6];
355 
356 	initcdb(cmd, sizeof cmd, ScmdStart);
357 	cmd[4] = code;
358 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
359 }
360 
361 Drive*
362 mmcprobe(Scsi *scsi)
363 {
364 	Mmcaux *aux;
365 	Drive *drive;
366 	uchar buf[Pagesz];
367 	int cap, n;
368 
369 	if (vflag)
370 		print("mmcprobe: inquiry: %s\n", scsi->inquire);
371 
372 	drive = emalloc(sizeof(Drive));
373 	drive->Scsi = *scsi;
374 	drive->Dev = mmcdev;
375 	drive->invistrack = -1;
376 	getinvistrack(drive);
377 
378 	aux = emalloc(sizeof(Mmcaux));
379 	drive->aux = aux;
380 
381 	scsiready(drive);
382 	drive->type = getdevtype(drive);
383 	if (drive->type != TypeCD) {
384 		werrstr("not an mmc device");
385 		free(aux);
386 		free(drive);
387 		return nil;
388 	}
389 
390 	/*
391 	 * drive is an mmc device; learn what we can about it
392 	 * (as opposed to the disc in it).
393 	 */
394 
395 	start(drive, 1);
396 	/* attempt to read CD capabilities page, but it's now legacy */
397 	if(mmcgetpage10(drive, Pagcapmechsts, buf) >= 0)
398 		aux->pagecmdsz = 10;
399 	else if(mmcgetpage6(drive, Pagcapmechsts, buf) >= 0)
400 		aux->pagecmdsz = 6;
401 	else {
402 		if (vflag)
403 			fprint(2, "no Pagcapmechsts mode page!\n");
404 		werrstr("can't read mode page %d!", Pagcapmechsts);
405 		free(aux);
406 		free(drive);
407 		return nil;
408 	}
409 
410 	cap = 0;
411 	if(buf[Capwrite] & (Capcdr|Capcdrw|Capdvdr|Capdvdram) ||
412 	    buf[Capmisc] & Caprw)
413 		cap |= Cwrite;
414 	if(buf[Capmisc] & Capcdda)	/* CD-DA commands supported? */
415 		cap |= Ccdda;		/* not used anywhere else */
416 
417 //	print("read %d max %d\n", biges(buf+14), biges(buf+8));
418 //	print("write %d max %d\n", biges(buf+20), biges(buf+18));
419 
420 	/* cache optional page 05 (write parameter page) */
421 	if(/* (cap & Cwrite) && */
422 	    mmcgetpage(drive, Pagwrparams, aux->page05) >= 0) {
423 		aux->page05ok = 1;
424 		cap |= Cwrite;
425 		if (vflag)
426 			fprint(2, "mmcprobe: got page 5, assuming drive can write\n");
427 	} else {
428 		if (vflag)
429 			fprint(2, "no Pagwrparams mode page!\n");
430 		cap &= ~Cwrite;
431 	}
432 	drive->cap = cap;
433 
434 	mmcgetspeed(drive);
435 
436 	/*
437 	 * we can't actually control caching much.
438 	 * see SBC-2 §6.3.3 but also MMC-6 §7.6.
439 	 */
440 	n = mmcgetpage(drive, Pagcache, buf);
441 	if (n >= 3) {
442 		/* n == 255; buf[1] == 10 (10 bytes after buf[1]) */
443 		buf[0] &= 077;		/* clear reserved bits, MMC-6 §7.2.3 */
444 		assert(buf[0] == Pagcache);
445 		assert(buf[1] >= 10);
446 		buf[2] = Ccwce;
447 		if (mmcsetpage(drive, Pagcache, buf) < 0)
448 			if (vflag)
449 				print("mmcprobe: cache control NOT set\n");
450 	}
451 	return drive;
452 }
453 
454 static char *tracktype[] = {	/* indices are track modes (Tm*) */
455 	"audio cdda",
456 	"2 audio channels",
457 	"2",
458 	"3",
459 	"data, recorded uninterrupted",
460 	"data, recorded interrupted",
461 };
462 
463 /* t is a track number on disc, i is an index into drive->track[] for result */
464 static int
465 mmctrackinfo(Drive *drive, int t, int i)
466 {
467 	int n, type, bs;
468 	long newnwa;
469 	ulong beg, size;
470 	uchar tmode;
471 	uchar cmd[10], resp[255];
472 	Mmcaux *aux;
473 
474 	aux = drive->aux;
475 	initcdb(cmd, sizeof cmd, ScmdRtrackinfo);
476 	cmd[1] = 1;			/* address below is logical track # */
477 	cmd[2] = t>>24;
478 	cmd[3] = t>>16;
479 	cmd[4] = t>>8;
480 	cmd[5] = t;
481 	cmd[7] = sizeof(resp)>>8;
482 	cmd[8] = sizeof(resp);
483 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
484 	if(n < 28) {
485 		if(vflag)
486 			print("trackinfo %d fails n=%d: %r\n", t, n);
487 		return -1;
488 	}
489 
490 	beg = bige(&resp[8]);
491 	size = bige(&resp[24]);
492 
493 	tmode = resp[5] & 0x0D;
494 //	dmode = resp[6] & 0x0F;
495 
496 	if(vflag)
497 		print("track %d type %d (%s)", t, tmode,
498 			(tmode < nelem(tracktype)? tracktype[tmode]: "**GOK**"));
499 	type = TypeNone;
500 	bs = BScdda;
501 	switch(tmode){
502 	case Tmcdda:
503 		type = TypeAudio;
504 		bs = BScdda;
505 		break;
506 	case Tm2audio:	/* 2 audio channels, with pre-emphasis 50/15 μs */
507 		if(vflag)
508 			print("audio channels with preemphasis on track %d "
509 				"(u%.3d)\n", t, i);
510 		type = TypeNone;
511 		break;
512 	case Tmunintr:		/* data track, recorded uninterrupted */
513 	case Tmintr:		/* data track, recorded interrupted */
514 		/* treat Tmintr (5) as cdrom; it's probably dvd or bd */
515 		type = TypeData;
516 		bs = BScdrom;
517 		break;
518 	default:
519 		if(vflag)
520 			print("unknown track type %d\n", tmode);
521 		break;
522 	}
523 
524 	drive->track[i].mtime = drive->changetime;
525 	drive->track[i].beg = beg;
526 	drive->track[i].end = beg+size;
527 	drive->track[i].type = type;
528 	drive->track[i].bs = bs;
529 	drive->track[i].size = (vlong)(size-2) * bs;	/* -2: skip lead out */
530 
531 	if(resp[6] & (1<<6)) {			/* blank? */
532 		drive->track[i].type = TypeBlank;
533 		drive->writeok = Yes;
534 	}
535 
536 	/*
537 	 * figure out the first writable block, if we can
538 	 */
539 	if(vflag)
540 		print(" start %lud end %lud", beg, beg + size - 1);
541 	if(resp[7] & 1) {			/* nwa valid? */
542 		newnwa = bige(&resp[12]);
543 		if (newnwa >= 0)
544 			if (aux->mmcnwa < 0)
545 				aux->mmcnwa = newnwa;
546 			else if (aux->mmcnwa != newnwa)
547 				fprint(2, "nwa is %ld but invis track starts blk %ld\n",
548 					newnwa, aux->mmcnwa);
549 	}
550 	/* resp[6] & (1<<7) of zero: invisible track */
551 	if(t == Invistrack || t == drive->invistrack)
552 		if (aux->mmcnwa < 0)
553 			aux->mmcnwa = beg;
554 		else if (aux->mmcnwa != beg)
555 			fprint(2, "invis track starts blk %ld but nwa is %ld\n",
556 				beg, aux->mmcnwa);
557 	if (vflag && aux->mmcnwa >= 0)
558 		print(" nwa %lud", aux->mmcnwa);
559 	if (vflag)
560 		print("\n");
561 	return 0;
562 }
563 
564 /* this may fail for blank media */
565 static int
566 mmcreadtoc(Drive *drive, int type, int track, void *data, int nbytes)
567 {
568 	uchar cmd[10];
569 
570 	initcdb(cmd, sizeof cmd, ScmdRTOC);
571 	cmd[1] = type;				/* msf bit & reserved */
572 	cmd[2] = Tocfmttoc;
573 	cmd[6] = track;				/* track/session */
574 	cmd[7] = nbytes>>8;
575 	cmd[8] = nbytes;
576 
577 	/*
578 	 * printing iounit(drive->Scsi.rawfd) here yields
579 	 *	iounit(3) = 0;		# for local access
580 	 *	iounit(3) = 65512;	# for remote access via /mnt/term
581 	 */
582 	return scsi(drive, cmd, sizeof(cmd), data, nbytes, Sread);
583 }
584 
585 static Msf
586 rdmsf(uchar *p)
587 {
588 	Msf msf;
589 
590 	msf.m = p[0];
591 	msf.s = p[1];
592 	msf.f = p[2];
593 	return msf;
594 }
595 
596 static int
597 getdiscinfo(Drive *drive, uchar resp[], int resplen)
598 {
599 	int n;
600 	uchar cmd[10];
601 
602 	initcdb(cmd, sizeof cmd, ScmdRdiscinfo);
603 	cmd[7] = resplen>>8;
604 	cmd[8] = resplen;
605 	n = scsi(drive, cmd, sizeof(cmd), resp, resplen, Sread);
606 	if(n < 24) {
607 		if(n >= 0)
608 			werrstr("rdiscinfo returns %d", n);
609 		else if (vflag)
610 			fprint(2, "read disc info failed\n");
611 		return -1;
612 	}
613 	if (vflag)
614 		fprint(2, "read disc info succeeded\n");
615 	assert((resp[2] & 0340) == 0);			/* data type 0 */
616 	drive->erasable = ((resp[2] & 0x10) != 0);	/* -RW? */
617 	return n;
618 }
619 
620 static int
621 getconf(Drive *drive)
622 {
623 	int n;
624 	ushort prof;
625 	ulong datalen;
626 	uchar cmd[10];
627 	uchar resp[2*1024];	/* 64k-8 ok, 2k often enough, 400 typical */
628 
629 	initcdb(cmd, sizeof cmd, Scmdgetconf);
630 	cmd[3] = 1;			/* start with core feature */
631 	cmd[7] = sizeof resp >> 8;
632 	cmd[8] = sizeof resp;
633 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread);
634 	if (n < 0) {
635 		if(vflag)
636 			fprint(2, "get config cmd failed\n");
637 		return -1;
638 	}
639 	if (n < 4)
640 		return -1;
641 	datalen = GETBELONG(resp+0);
642 	if (datalen < 8)
643 		return -1;
644 	/*
645 	 * features start with an 8-byte header:
646 	 * ulong datalen, ushort reserved, ushort current profile.
647 	 * profile codes (table 92) are: 0 reserved, 1-7 legacy, 8-0xf cd,
648 	 * 0x10-0x1f 0x2a-0x2b dvd*, 0x40-0x4f bd, 0x50-0x5f hd dvd,
649 	 * 0xffff whacko.
650 	 *
651 	 * this is followed by multiple feature descriptors:
652 	 * ushort code, uchar bits, uchar addnl_len, addnl_len bytes.
653 	 */
654 	prof = resp[6]<<8 | resp[7];
655 	if(prof == 0 || prof == 0xffff)	/* none or whacko? */
656 		return n;
657 	if(drive->mmctype != Mmcnone)
658 		return n;
659 	switch (prof >> 4) {
660 	case 0:
661 		drive->mmctype = Mmccd;
662 		break;
663 	case 1:
664 		if (prof == 0x1a || prof == 0x1b)
665 			drive->mmctype = Mmcdvdplus;
666 		else
667 			drive->mmctype = Mmcdvdminus;
668 		break;
669 	case 2:
670 		drive->mmctype = Mmcdvdplus;	/* dual layer */
671 		break;
672 	case 4:
673 		drive->mmctype = Mmcbd;
674 		/*
675 		 * further decode prof to set writability flags.
676 		 * mostly for Pioneer BDR-206M.  there may be unnecessary
677 		 * future profiles for dual, triple and quad layer;
678 		 * let's hope not.
679 		 */
680 		switch (prof) {
681 		case 0x40:
682 			drive->erasable = drive->recordable = No;
683 		case 0x41:
684 		case 0x42:
685 			drive->erasable = No;
686 			drive->recordable = Yes;
687 			break;
688 		case 0x43:
689 			drive->erasable = Yes;
690 			drive->recordable = No;
691 			break;
692 		}
693 		break;
694 	case 5:
695 		drive->mmctype = Mmcdvdminus;	/* hd dvd, obs. */
696 		break;
697 	}
698 	return n;
699 }
700 
701 static int
702 getdvdstruct(Drive *drive)
703 {
704 	int n, cat;
705 	uchar cmd[12], resp[Pagesz];
706 
707 	initcdb(cmd, sizeof cmd, ScmdReadDVD); /* actually, read disc structure */
708 	cmd[1] = 0;			/* media type: dvd */
709 	cmd[7] = 0;			/* format code: physical format */
710 	cmd[8] = sizeof resp >> 8;	/* allocation length */
711 	cmd[9] = sizeof resp;
712 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread);
713 	if (n < 7) {
714 		if(vflag)
715 			fprint(2, "read disc structure (dvd) cmd failed\n");
716 		return -1;
717 	}
718 
719 	/* resp[0..1] is resp length */
720 	cat = (resp[4] & 0xf0) >> 4;	/* disk category, MMC-6 §6.22.3.2.1 */
721 	if (vflag)
722 		fprint(2, "dvd type is %s\n", dvdtype[cat]);
723 	drive->dvdtype = dvdtype[cat];
724 	/* write parameters mode page may suffice to compute writeok for dvd */
725 	drive->erasable = drive->recordable = No;
726 	/*
727 	 * the layer-type field is a *bit array*,
728 	 * though an enumeration of types would make more sense,
729 	 * since the types are exclusive, not orthogonal.
730 	 */
731 	if (resp[6] & (1<<2))			/* rewritable? */
732 		drive->erasable = Yes;
733 	else if (resp[6] & (1<<1))		/* recordable once? */
734 		drive->recordable = Yes;
735 	/* else it's a factory-pressed disk */
736 	drive->mmctype = (cat >= 8? Mmcdvdplus: Mmcdvdminus);
737 	return 0;
738 }
739 
740 /*
741  * ugly hack to divine device type from inquiry string as last resort.
742  * mostly for Pioneer BDR-206M.
743  */
744 static int
745 bdguess(Drive *drive)
746 {
747 	if (drive->mmctype == Mmcnone) {
748 		if (strstr(drive->Scsi.inquire, "BD") == nil)
749 			return -1;
750 		if (vflag)
751 			fprint(2, "drive probably a BD (from inquiry string)\n");
752 		drive->mmctype = Mmcbd;
753 	} else if (drive->mmctype == Mmcbd) {
754 		if (drive->erasable != Unset && drive->recordable != Unset)
755 			return 0;
756 	} else
757 		return -1;
758 
759 	drive->recordable = drive->writeok = No;
760 	if (strstr(drive->Scsi.inquire, "RW") != nil) {
761 		if (vflag)
762 			fprint(2, "drive probably a burner (from inquiry string)\n");
763 		drive->recordable = drive->writeok = Yes;
764 		if (drive->erasable == Unset) {	/* set by getdiscinfo perhaps */
765 			drive->erasable = No;	/* no way to tell, alas */
766 			if (vflag)
767 				fprint(2, "\tassuming -r not -rw\n");
768 		}
769 	} else {
770 		if (drive->erasable == Unset)
771 			drive->erasable = No;
772 	}
773 	if (drive->erasable == Yes)
774 		drive->recordable = No;		/* mutually exclusive */
775 	return 0;
776 }
777 
778 static int
779 getbdstruct(Drive *drive)
780 {
781 	int n;
782 	uchar cmd[12], resp[4+4096];
783 	uchar *di, *body;
784 
785 	initcdb(cmd, sizeof cmd, ScmdReadDVD); /* actually, read disc structure */
786 	cmd[1] = 1;			/* media type: bd */
787 	/* cmd[6] is layer #, 0 is first */
788 	cmd[7] = 0;			/* format code: disc info */
789 	cmd[8] = sizeof resp >> 8;	/* allocation length */
790 	cmd[9] = sizeof resp;
791 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread);
792 	if(n < 0) {
793 		if(vflag)
794 			fprint(2, "read disc structure (bd) cmd failed\n");
795 		return -1;
796 	}
797 
798 	/*
799 	 * resp[0..1] is resp length (4100); 2 & 3 are reserved.
800 	 * there may be multiple disc info structs of 112 bytes each.
801 	 * disc info (di) starts at 4.  di[0..7] are header, followed by body.
802 	 * body[0..2] is bd type (disc type identifier):
803 	 * BDO|BDW|BDR, MMC-6 §6.22.3.3.1.  The above scsi command should
804 	 * fail on DVD drives, but some seem to ignore media type
805 	 * and return successfully, so verify that it's a BD drive.
806 	 */
807 	di = resp + 4;
808 	body = di + 8;
809 	n -= 4 + 8;
810 	if (n < 3 || di[0] != 'D' || di[1] != 'I' ||
811 	    body[0] != 'B' || body[1] != 'D') {
812 		if(vflag)
813 			fprint(2, "it's not a bd\n");
814 		return -1;
815 	}
816 	if (vflag)
817 		fprint(2, "read disc structure (bd) succeeded; di format %d\n",
818 			di[2]);
819 
820 	drive->erasable = drive->recordable = No;
821 	switch (body[2]) {
822 	case 'O':				/* read-Only */
823 		break;
824 	case 'R':				/* Recordable */
825 		drive->recordable = Yes;
826 		break;
827 	case 'W':				/* reWritable */
828 		drive->erasable = Yes;
829 		break;
830 	default:
831 		fprint(2, "%s: unknown bd type BD%c\n", argv0, body[2]);
832 		return -1;
833 	}
834 	drive->mmctype = Mmcbd;
835 	return 0;
836 }
837 
838 /*
839  * infer endings from the beginnings of other tracks.
840  */
841 static void
842 mmcinfertracks(Drive *drive, int first, int last)
843 {
844 	int i;
845 	uchar resp[1024];
846 	ulong tot;
847 	Track *t;
848 
849 	if (vflag)
850 		fprint(2, "inferring tracks\n");
851 	for(i = first; i <= last; i++) {
852 		memset(resp, 0, sizeof(resp));
853 		if(mmcreadtoc(drive, 0, i, resp, sizeof(resp)) < 0)
854 			break;
855 		t = &drive->track[i-first];
856 		t->mtime = drive->changetime;
857 		t->type = TypeData;
858 		t->bs = BScdrom;
859 		t->beg = bige(resp+8);
860 		if(!(resp[5] & 4)) {
861 			t->type = TypeAudio;
862 			t->bs = BScdda;
863 		}
864 	}
865 
866 	if((long)drive->track[0].beg < 0)  /* i've seen negative track 0's */
867 		drive->track[0].beg = 0;
868 
869 	tot = 0;
870 	memset(resp, 0, sizeof(resp));
871 	/* 0xAA is lead-out */
872 	if(mmcreadtoc(drive, 0, 0xAA, resp, sizeof(resp)) < 0)
873 		print("bad\n");
874 	if(resp[6])
875 		tot = bige(resp+8);
876 
877 	for(i=last; i>=first; i--) {
878 		t = &drive->track[i-first];
879 		t->end = tot;
880 		tot = t->beg;
881 		if(t->end <= t->beg)
882 			t->beg = t->end = 0;
883 		/* -2: skip lead out */
884 		t->size = (t->end - t->beg - 2) * (vlong)t->bs;
885 	}
886 }
887 
888 /* this gets called a lot from main.c's 9P routines */
889 static int
890 mmcgettoc(Drive *drive)
891 {
892 	int i, n, first, last;
893 	uchar resp[1024];
894 	Mmcaux *aux;
895 
896 	/*
897 	 * if someone has swapped the cd,
898 	 * mmcreadtoc will get ``medium changed'' and the
899 	 * scsi routines will set nchange and changetime in the
900 	 * scsi device.
901 	 */
902 	mmcreadtoc(drive, 0, 0, resp, sizeof(resp));
903 	if(drive->Scsi.changetime == 0) {	/* no media present */
904 		drive->mmctype = Mmcnone;
905 		drive->ntrack = 0;
906 		return 0;
907 	}
908 	/*
909 	 * if the disc doesn't appear to be have been changed, and there
910 	 * is a disc in this drive, there's nothing to do (the common case).
911 	 */
912 	if(drive->nchange == drive->Scsi.nchange && drive->changetime != 0)
913 		return 0;
914 
915 	/*
916 	 * the disc in the drive may have just been changed,
917 	 * so rescan it and relearn all about it.
918 	 */
919 
920 	drive->ntrack = 0;
921 	drive->nameok = 0;
922 	drive->nchange = drive->Scsi.nchange;
923 	drive->changetime = drive->Scsi.changetime;
924 	drive->writeok = No;
925 	drive->erasable = drive->recordable = Unset;
926 	getinvistrack(drive);
927 	aux = drive->aux;
928 	aux->mmcnwa = -1;
929 	aux->nropen = aux->nwopen = 0;
930 	aux->ntotby = aux->ntotbk = 0;
931 
932 	for(i=0; i<nelem(drive->track); i++){
933 		memset(&drive->track[i].mbeg, 0, sizeof(Msf));
934 		memset(&drive->track[i].mend, 0, sizeof(Msf));
935 	}
936 
937 	/*
938 	 * should set read ahead, MMC-6 §6.37, seems to control caching.
939 	 */
940 
941 	/*
942 	 * find number of tracks
943 	 */
944 	if((n = mmcreadtoc(drive, Msfbit, 0, resp, sizeof(resp))) < 4) {
945 		/*
946 		 * it could be a blank disc.  in case it's a blank disc in a
947 		 * cd-rw drive, use readdiscinfo to try to find the track info.
948 		 */
949 		if(getdiscinfo(drive, resp, sizeof(resp)) < 7)
950 			return -1;
951 		assert((resp[2] & 0340) == 0);	/* data type 0 */
952 		if(resp[4] != 1)
953 			print("multi-session disc %d\n", resp[4]);
954 		first = resp[3];
955 		last = resp[6];
956 		if(vflag)
957 			print("tracks %d-%d\n", first, last);
958 		drive->writeok = Yes;
959 	} else {
960 		first = resp[2];
961 		last = resp[3];
962 
963 		if(n >= 4+8*(last-first+2)) {
964 			/* resp[4 + i*8 + 2] is track # */
965 			/* <=: track[last-first+1] = end */
966 			for(i=0; i<=last-first+1; i++)
967 				drive->track[i].mbeg = rdmsf(resp+4+i*8+5);
968 			for(i=0; i<last-first+1; i++)
969 				drive->track[i].mend = drive->track[i+1].mbeg;
970 		}
971 	}
972 
973 	/* deduce disc type */
974 	drive->mmctype = Mmcnone;
975 	drive->dvdtype = nil;
976 	getdvdstruct(drive);
977 	getconf(drive);
978 	getbdstruct(drive);
979 	if (Desperate)
980 		bdguess(drive);
981 	if (drive->mmctype == Mmcnone)
982 		drive->mmctype = Mmccd;		/* by default */
983 	if (drive->recordable == Yes || drive->erasable == Yes)
984 		drive->writeok = Yes;
985 
986 	if (vflag) {
987 		fprint(2, "writeok %d", drive->writeok);
988 		if (drive->recordable != Unset)
989 			fprint(2, " recordable %d", drive->recordable);
990 		if (drive->erasable != Unset)
991 			fprint(2, " erasable %d", drive->erasable);
992 		fprint(2, "\n");
993 		fprint(2, "first %d last %d\n", first, last);
994 		fprint(2, "it's a %s disc.\n\n", disctype(drive)); /* leak */
995 	}
996 
997 	if(first == 0 && last == 0)
998 		first = 1;
999 
1000 	if(first <= 0 || first >= Maxtrack) {
1001 		werrstr("first table %d not in range", first);
1002 		return -1;
1003 	}
1004 	if(last <= 0 || last >= Maxtrack) {
1005 		werrstr("last table %d not in range", last);
1006 		return -1;
1007 	}
1008 
1009 	if(drive->cap & Cwrite)			/* CDR drives are easy */
1010 		for(i = first; i <= last; i++)
1011 			mmctrackinfo(drive, i, i - first);
1012 	else
1013 		/*
1014 		 * otherwise we need to infer endings from the
1015 		 * beginnings of other tracks.
1016 		 */
1017 		mmcinfertracks(drive, first, last);
1018 
1019 	drive->firsttrack = first;
1020 	drive->ntrack = last+1-first;
1021 	return 0;
1022 }
1023 
1024 static void
1025 settrkmode(uchar *p, int mode)
1026 {
1027 	p[Wptrkmode] &= ~0xf;
1028 	p[Wptrkmode] |= mode;
1029 }
1030 
1031 /*
1032  * this uses page 5, which is optional.
1033  */
1034 static int
1035 mmcsetbs(Drive *drive, int bs)
1036 {
1037 	uchar *p;
1038 	Mmcaux *aux;
1039 
1040 	aux = drive->aux;
1041 	if (!aux->page05ok)
1042 		return 0;			/* harmless; assume 2k */
1043 
1044 	p = aux->page05;
1045 	/*
1046 	 * establish defaults.
1047 	 */
1048 	p[0] &= 077;			/* clear reserved bits, MMC-6 §7.2.3 */
1049 	p[Wpwrtype] = Bufe | Wttrackonce;
1050 //	if(testonlyflag)
1051 //		p[Wpwrtype] |= 0x10;	/* test-write */
1052 	/* assume dvd values as defaults */
1053 	settrkmode(p, Tmintr);
1054 	p[Wptrkmode] |= Msnext;
1055 	p[Wpdatblktype] = Db2kdata;
1056 	p[Wpsessfmt] = Sfdata;
1057 
1058 	switch(drive->mmctype) {
1059 	case Mmcdvdplus:
1060 	case Mmcbd:
1061 		break;
1062 	case Mmcdvdminus:
1063 		/* dvd-r can only do disc-at-once or incremental */
1064 		p[Wpwrtype] = Bufe | Wtsessonce;
1065 		break;
1066 	case Mmccd:
1067 		settrkmode(p, Tmunintr); /* data track, uninterrupted */
1068 		switch(bs){
1069 		case BScdda:
1070 			/* 2 audio channels without pre-emphasis */
1071 			settrkmode(p, Tmcdda);	/* should be Tm2audio? */
1072 			p[Wpdatblktype] = Dbraw;
1073 			break;
1074 		case BScdrom:
1075 			break;
1076 		case BScdxa:
1077 			p[Wpdatblktype] = Db2336;
1078 			p[Wpsessfmt] = Sfcdxa;
1079 			break;
1080 		default:
1081 			fprint(2, "%s: unknown CD type; bs %d\n", argv0, bs);
1082 			assert(0);
1083 		}
1084 		break;
1085 	default:
1086 		fprint(2, "%s: unknown disc sub-type %d\n",
1087 			argv0, drive->mmctype);
1088 		break;
1089 	}
1090 	if(mmcsetpage(drive, Pagwrparams, p) < 0) {
1091 		if (vflag)
1092 			fprint(2, "mmcsetbs: could NOT set write parameters page\n");
1093 		return -1;
1094 	}
1095 	return 0;
1096 }
1097 
1098 /* off is a block number */
1099 static long
1100 mmcread(Buf *buf, void *v, long nblock, ulong off)
1101 {
1102 	int bs;
1103 	long n, nn;
1104 	uchar cmd[12];
1105 	Drive *drive;
1106 	Otrack *o;
1107 
1108 	o = buf->otrack;
1109 	drive = o->drive;
1110 	bs = o->track->bs;
1111 	off += o->track->beg;
1112 
1113 	if(nblock >= (1<<10)) {
1114 		werrstr("mmcread too big");
1115 		if(vflag)
1116 			fprint(2, "mmcread too big\n");
1117 		return -1;
1118 	}
1119 
1120 	/* truncate nblock modulo size of track */
1121 	if(off > o->track->end - 2) {
1122 		werrstr("read past end of track");
1123 		if(vflag)
1124 			fprint(2, "end of track (%ld->%ld off %ld)",
1125 				o->track->beg, o->track->end - 2, off);
1126 		return -1;
1127 	}
1128 	if(off == o->track->end - 2)
1129 		return 0;
1130 
1131 	if(off+nblock > o->track->end - 2)
1132 		nblock = o->track->end - 2 - off;
1133 
1134 	/*
1135 	 * `read cd' only works for CDs; for everybody else,
1136 	 * we'll try plain `read (12)'.  only use read cd if it's
1137 	 * a cd drive with a cd in it and we're not reading data
1138 	 * (e.g., reading audio).
1139 	 */
1140 	if (drive->type == TypeCD && drive->mmctype == Mmccd && bs != BScdrom) {
1141 		initcdb(cmd, sizeof cmd, ScmdReadcd);
1142 		cmd[2] = off>>24;
1143 		cmd[3] = off>>16;
1144 		cmd[4] = off>>8;
1145 		cmd[5] = off>>0;
1146 		cmd[6] = nblock>>16;
1147 		cmd[7] = nblock>>8;
1148 		cmd[8] = nblock>>0;
1149 		cmd[9] = 0x10;
1150 		switch(bs){
1151 		case BScdda:
1152 			cmd[1] = 0x04;
1153 			break;
1154 		case BScdrom:
1155 			cmd[1] = 0x08;
1156 			break;
1157 		case BScdxa:
1158 			cmd[1] = 0x0C;
1159 			break;
1160 		default:
1161 			werrstr("unknown bs %d", bs);
1162 			return -1;
1163 		}
1164 	} else {			/* e.g., TypeDA */
1165 		initcdb(cmd, sizeof cmd, ScmdRead12);
1166 		cmd[2] = off>>24;
1167 		cmd[3] = off>>16;
1168 		cmd[4] = off>>8;
1169 		cmd[5] = off>>0;
1170 		cmd[6] = nblock>>24;
1171 		cmd[7] = nblock>>16;
1172 		cmd[8] = nblock>>8;
1173 		cmd[9] = nblock;
1174 		// cmd[10] = 0x80;	/* streaming */
1175 	}
1176 	n = nblock*bs;
1177 	nn = scsi(drive, cmd, sizeof(cmd), v, n, Sread);
1178 	if(nn != n) {
1179 		if(nn != -1)
1180 			werrstr("short read %ld/%ld", nn, n);
1181 		if(vflag)
1182 			print("read off %lud nblock %ld bs %d failed\n",
1183 				off, nblock, bs);
1184 		return -1;
1185 	}
1186 
1187 	return nblock;
1188 }
1189 
1190 static Otrack*
1191 mmcopenrd(Drive *drive, int trackno)
1192 {
1193 	Otrack *o;
1194 	Mmcaux *aux;
1195 
1196 	if(trackno < 0 || trackno >= drive->ntrack) {
1197 		werrstr("track number out of range");
1198 		return nil;
1199 	}
1200 
1201 	aux = drive->aux;
1202 	if(aux->nwopen) {
1203 		werrstr("disk in use for writing");
1204 		return nil;
1205 	}
1206 
1207 	o = emalloc(sizeof(Otrack));
1208 	o->drive = drive;
1209 	o->track = &drive->track[trackno];
1210 	o->nchange = drive->nchange;
1211 	o->omode = OREAD;
1212 	o->buf = bopen(mmcread, OREAD, o->track->bs, Readblock);
1213 	o->buf->otrack = o;
1214 
1215 	aux->nropen++;
1216 
1217 	return o;
1218 }
1219 
1220 static int
1221 format(Drive *drive)
1222 {
1223 	ulong nblks, blksize;
1224 	uchar *fmtdesc;
1225 	uchar cmd[6], parms[4+8];
1226 
1227 	if (drive->recordable == Yes && drive->mmctype != Mmcbd) {
1228 		werrstr("don't format write-once cd or dvd media");
1229 		return -1;
1230 	}
1231 	initcdb(cmd, sizeof cmd, ScmdFormat);	/* format unit */
1232 	cmd[1] = 0x10 | 1;		/* format data, mmc format code */
1233 
1234 	memset(parms, 0, sizeof parms);
1235 	/* format list header */
1236 	parms[1] = 2;			/* immediate return; don't wait */
1237 	parms[3] = 8;			/* format descriptor length */
1238 
1239 	fmtdesc = parms + 4;
1240 	fmtdesc[4] = 0;			/* full format */
1241 
1242 	nblks = 0;
1243 	blksize = BScdrom;
1244 	switch (drive->mmctype) {
1245 	case Mmccd:
1246 		nblks = 0;
1247 		break;
1248 	case Mmcdvdplus:
1249 		/* format type 0 is optional but equiv to format type 0x26 */
1250 		fmtdesc[4] = 0x26 << 2;
1251 		nblks = ~0ul;
1252 		blksize = 0;
1253 		break;
1254 	}
1255 
1256 	PUTBELONG(fmtdesc, nblks);
1257 	PUTBE24(fmtdesc + 5, blksize);
1258 
1259 //	print("format parameters:\n");
1260 //	hexdump(parms, sizeof parms);
1261 
1262 	if(vflag)
1263 		print("%lld ns: format\n", nsec());
1264 	return scsi(drive, cmd, sizeof(cmd), parms, sizeof parms, Swrite);
1265 }
1266 
1267 static long
1268 mmcxwrite(Otrack *o, void *v, long nblk)
1269 {
1270 	int r;
1271 	uchar cmd[10];
1272 	Mmcaux *aux;
1273 
1274 	assert(o->omode == OWRITE);
1275 	aux = o->drive->aux;
1276 	if (aux->mmcnwa == -1 && scsiready(o->drive) < 0) {
1277 		werrstr("device not ready to write");
1278 		return -1;
1279 	}
1280 	aux->ntotby += nblk*o->track->bs;
1281 	aux->ntotbk += nblk;
1282 
1283 	initcdb(cmd, sizeof cmd, ScmdExtwrite);	/* write (10) */
1284 	cmd[2] = aux->mmcnwa>>24;
1285 	cmd[3] = aux->mmcnwa>>16;
1286 	cmd[4] = aux->mmcnwa>>8;
1287 	cmd[5] = aux->mmcnwa;
1288 	cmd[7] = nblk>>8;
1289 	cmd[8] = nblk>>0;
1290 	if(vflag)
1291 		print("%lld ns: write %ld at 0x%lux\n",
1292 			nsec(), nblk, aux->mmcnwa);
1293 	r = scsi(o->drive, cmd, sizeof(cmd), v, nblk*o->track->bs, Swrite);
1294 	if (r < 0)
1295 		fprint(2, "%s: write error at blk offset %,ld = "
1296 			"offset %,lld / bs %ld: %r\n",
1297 			argv0, aux->mmcnwa, (vlong)aux->mmcnwa * o->track->bs,
1298 			o->track->bs);
1299 	aux->mmcnwa += nblk;
1300 	return r;
1301 }
1302 
1303 static long
1304 mmcwrite(Buf *buf, void *v, long nblk, ulong)
1305 {
1306 	return mmcxwrite(buf->otrack, v, nblk);
1307 }
1308 
1309 enum {
1310 	Eccblk	= 128,		/* sectors per ecc block */
1311 	Rsvslop	= 0,
1312 };
1313 
1314 static int
1315 reserve(Drive *drive, int track)
1316 {
1317 	ulong sz;
1318 	uchar cmd[10];
1319 
1320 	initcdb(cmd, sizeof cmd, ScmdReserve);
1321 	track -= drive->firsttrack;		/* switch to zero-origin */
1322 	if (track >= 0 && track < drive->ntrack)
1323 		/* .end is next sector past sz */
1324 		sz = drive->track[track].end - drive->track[track].beg - Rsvslop;
1325 	else {
1326 		sz = Eccblk;
1327 		fprint(2, "%s: reserve: track #%d out of range 0-%d\n",
1328 			argv0, track, drive->ntrack);
1329 	}
1330 	sz -= sz % Eccblk;		/* round down to ecc-block multiple */
1331 	if ((long)sz < 0) {
1332 		fprint(2, "%s: reserve: bogus size %lud\n", argv0, sz);
1333 		return -1;
1334 	}
1335 	cmd[1] = 0;			/* no ASRV: allocate by size not lba */
1336 	PUTBELONG(cmd + 2 + 3, sz);
1337 	if (vflag)
1338 		fprint(2, "reserving %ld sectors\n", sz);
1339 	return scsi(drive, cmd, sizeof cmd, cmd, 0, Snone);
1340 }
1341 
1342 static int
1343 getinvistrack(Drive *drive)
1344 {
1345 	int n;
1346 	uchar cmd[10], resp[Pagesz];
1347 
1348 	initcdb(cmd, sizeof(cmd), ScmdRtrackinfo);
1349 	cmd[1] = 1<<2 | 1;	/* open; address below is logical track # */
1350 	PUTBELONG(cmd + 2, 1);		/* find first open track */
1351 	cmd[7] = sizeof(resp)>>8;
1352 	cmd[8] = sizeof(resp);
1353 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
1354 	if(n < 4) {
1355 		if(vflag)
1356 			print("trackinfo for invis track fails n=%d: %r\n", n);
1357 		return -1;
1358 	}
1359 
1360 	if(vflag)
1361 		print("getinvistrack: track #%d session #%d\n",
1362 			resp[2], resp[3]);
1363 	drive->invistrack = resp[2];
1364 	return resp[2];
1365 }
1366 
1367 static Otrack*
1368 mmccreate(Drive *drive, int type)
1369 {
1370 	int bs;
1371 	Mmcaux *aux;
1372 	Track *t;
1373 	Otrack *o;
1374 
1375 	aux = drive->aux;
1376 
1377 	if(aux->nropen || aux->nwopen) {
1378 		werrstr("drive in use");
1379 		return nil;
1380 	}
1381 
1382 	switch(type){
1383 	case TypeAudio:
1384 		bs = BScdda;
1385 		break;
1386 	case TypeData:
1387 		bs = BScdrom;
1388 		break;
1389 	default:
1390 		werrstr("bad type %d", type);
1391 		return nil;
1392 	}
1393 
1394 	/* comment out the returns for now; it should be no big deal - geoff */
1395 	if(mmctrackinfo(drive, drive->invistrack, Maxtrack)) {
1396 		if (vflag)
1397 			fprint(2, "mmccreate: mmctrackinfo for invis track %d"
1398 				" failed: %r\n", drive->invistrack);
1399 		werrstr("disc not writable");
1400 //		return nil;
1401 	}
1402 	if(mmcsetbs(drive, bs) < 0) {
1403 		werrstr("cannot set bs mode");
1404 //		return nil;
1405 	}
1406 	if(mmctrackinfo(drive, drive->invistrack, Maxtrack)) {
1407 		if (vflag)
1408 			fprint(2, "mmccreate: mmctrackinfo for invis track %d"
1409 				" (2) failed: %r\n", drive->invistrack);
1410 		werrstr("disc not writable 2");
1411 //		return nil;
1412 	}
1413 
1414 	/* special hack for dvd-r: reserve the invisible track */
1415 	if (drive->mmctype == Mmcdvdminus && drive->writeok &&
1416 	    drive->recordable == Yes && reserve(drive, drive->invistrack) < 0) {
1417 		if (vflag)
1418 			fprint(2, "mmcreate: reserving track %d for dvd-r "
1419 				"failed: %r\n", drive->invistrack);
1420 		return nil;
1421 	}
1422 
1423 	aux->ntotby = 0;
1424 	aux->ntotbk = 0;
1425 
1426 	t = &drive->track[drive->ntrack++];
1427 	t->size = 0;
1428 	t->bs = bs;
1429 	t->beg = aux->mmcnwa;
1430 	t->end = 0;
1431 	t->type = type;
1432 	drive->nameok = 0;
1433 
1434 	o = emalloc(sizeof(Otrack));
1435 	o->drive = drive;
1436 	o->nchange = drive->nchange;
1437 	o->omode = OWRITE;
1438 	o->track = t;
1439 	o->buf = bopen(mmcwrite, OWRITE, bs, Readblock);
1440 	o->buf->otrack = o;
1441 
1442 	aux->nwopen++;
1443 
1444 	if(vflag)
1445 		print("mmcinit: nwa = 0x%luX\n", aux->mmcnwa);
1446 	return o;
1447 }
1448 
1449 /*
1450  * issue some form of close track, close session or finalize disc command.
1451  * see The Matrix, table 252 in MMC-6 §6.3.2.3.
1452  */
1453 static int
1454 mmcxclose(Drive *drive, int clf, int trackno)
1455 {
1456 	uchar cmd[10];
1457 
1458 	initcdb(cmd, sizeof cmd, ScmdClosetracksess);
1459 	/* cmd[1] & 1 is the immediate bit */
1460 	cmd[2] = clf;				/* close function */
1461 	if(clf == Closetrack)
1462 		cmd[5] = trackno;
1463 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
1464 }
1465 
1466 /* flush drive cache, close current track */
1467 void
1468 mmcsynccache(Drive *drive)
1469 {
1470 	uchar cmd[10];
1471 	Mmcaux *aux;
1472 
1473 	initcdb(cmd, sizeof cmd, ScmdSynccache);
1474 	/*
1475 	 * this will take a long time to burn the remainder of a dvd-r
1476 	 * with a reserved track covering the whole disc.
1477 	 */
1478 	if (vflag) {
1479 		fprint(2, "syncing cache");
1480 		if (drive->mmctype == Mmcdvdminus && drive->writeok &&
1481 		    drive->recordable == Yes)
1482 			fprint(2, "; dvd-r burning rest of track reservation, "
1483 				"will be slow");
1484 		fprint(2, "\n");
1485 	}
1486 	scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
1487 	if(vflag) {
1488 		aux = drive->aux;
1489 		print("mmcsynccache: bytes = %lld blocks = %ld, mmcnwa 0x%luX\n",
1490 			aux->ntotby, aux->ntotbk, aux->mmcnwa);
1491 	}
1492 
1493 	/*
1494 	 * rsc: seems not to work on some drives.
1495 	 * so ignore return code & don't issue on dvd+rw.
1496 	 */
1497 	if(drive->mmctype != Mmcdvdplus || drive->erasable == No) {
1498 		if (vflag)
1499 			fprint(2, "closing invisible track %d (not dvd+rw)...\n",
1500 				drive->invistrack);
1501  		mmcxclose(drive, Closetrack, drive->invistrack);
1502 		if (vflag)
1503 			fprint(2, "... done.\n");
1504 	}
1505 	getinvistrack(drive);		/* track # has probably changed */
1506 }
1507 
1508 /*
1509  * close the open track `o'.
1510  */
1511 static void
1512 mmcclose(Otrack *o)
1513 {
1514 	Mmcaux *aux;
1515 	static uchar zero[2*BSmax];
1516 
1517 	aux = o->drive->aux;
1518 	if(o->omode == OREAD)
1519 		aux->nropen--;
1520 	else if(o->omode == OWRITE) {
1521 		aux->nwopen--;
1522 		mmcxwrite(o, zero, 2);	/* write lead out */
1523 		mmcsynccache(o->drive);
1524 		o->drive->nchange = -1;	/* force reread toc */
1525 	}
1526 	free(o);
1527 }
1528 
1529 static int
1530 setonesess(Drive *drive)
1531 {
1532 	uchar *p;
1533 	Mmcaux *aux;
1534 
1535 	/* page 5 is legacy and now read-only; see MMC-6 §7.5.4.1 */
1536 	aux = drive->aux;
1537 	p = aux->page05;
1538 	p[Wptrkmode] &= ~Msbits;
1539 	p[Wptrkmode] |= Msnonext;
1540 	return mmcsetpage(drive, Pagwrparams, p);
1541 }
1542 
1543 /*
1544  * close the current session, then finalize the disc.
1545  */
1546 static int
1547 mmcfixate(Drive *drive)
1548 {
1549 	int r;
1550 
1551 	if((drive->cap & Cwrite) == 0) {
1552 		werrstr("not a writer");
1553 		return -1;
1554 	}
1555 	drive->nchange = -1;		/* force reread toc */
1556 
1557 	setonesess(drive);
1558 
1559 	/* skip explicit close session on bd-r */
1560 	if (drive->mmctype != Mmcbd || drive->erasable == Yes) {
1561 		if (vflag)
1562 			fprint(2, "closing session and maybe finalizing...\n");
1563 		r = mmcxclose(drive, Closesessfinal, 0);
1564 		if (vflag)
1565 			fprint(2, "... done.\n");
1566 		if (r < 0)
1567 			return r;
1568 	}
1569 	/*
1570 	 * Closesessfinal only closes & doesn't finalize on dvd+r and bd-r.
1571 	 * Closedvdrbdfinal closes & finalizes dvd+r and bd-r.
1572 	 */
1573 	if ((drive->mmctype == Mmcdvdplus || drive->mmctype == Mmcbd) &&
1574 	    drive->erasable == No) {
1575 		if (vflag)
1576 			fprint(2, "finalizing dvd+r or bd-r... "
1577 				"(won't print `done').\n");
1578 		return mmcxclose(drive, Closedvdrbdfinal, 0);
1579 	}
1580 	return 0;
1581 }
1582 
1583 static int
1584 mmcblank(Drive *drive, int quick)
1585 {
1586 	uchar cmd[12];
1587 
1588 	drive->nchange = -1;		/* force reread toc */
1589 
1590 	initcdb(cmd, sizeof cmd, ScmdBlank);	/* blank cd-rw media */
1591 	/* immediate bit is 0x10 */
1592 	/* cmd[1] = 0 means blank the whole disc; = 1 just the header */
1593 	cmd[1] = quick ? 0x01 : 0x00;
1594 
1595 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
1596 }
1597 
1598 static char*
1599 e(int status)
1600 {
1601 	if(status < 0)
1602 		return geterrstr();
1603 	return nil;
1604 }
1605 
1606 static char*
1607 mmcctl(Drive *drive, int argc, char **argv)
1608 {
1609 	char *cmd;
1610 
1611 	if(argc < 1)
1612 		return nil;
1613 	cmd = argv[0];
1614 	if(strcmp(cmd, "format") == 0)
1615 		return e(format(drive));
1616 	if(strcmp(cmd, "blank") == 0)
1617 		return e(mmcblank(drive, 0));
1618 	if(strcmp(cmd, "quickblank") == 0)
1619 		return e(mmcblank(drive, 1));
1620 	if(strcmp(cmd, "eject") == 0)
1621 		return e(start(drive, 2));
1622 	if(strcmp(cmd, "ingest") == 0)
1623 		return e(start(drive, 3));
1624 	return "bad arg";
1625 }
1626 
1627 static char*
1628 mmcsetspeed(Drive *drive, int r, int w)
1629 {
1630 	char *rv;
1631 	uchar cmd[12];
1632 
1633 	initcdb(cmd, sizeof cmd, ScmdSetcdspeed);
1634 	cmd[2] = r>>8;
1635 	cmd[3] = r;
1636 	cmd[4] = w>>8;
1637 	cmd[5] = w;
1638 	rv = e(scsi(drive, cmd, sizeof(cmd), nil, 0, Snone));
1639 	mmcgetspeed(drive);
1640 	return rv;
1641 }
1642 
1643 static Dev mmcdev = {
1644 	mmcopenrd,
1645 	mmccreate,
1646 	bufread,
1647 	bufwrite,
1648 	mmcclose,
1649 	mmcgettoc,
1650 	mmcfixate,
1651 	mmcctl,
1652 	mmcsetspeed,
1653 };
1654