xref: /plan9-contrib/sys/src/cmd/cdfs/mmc.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
1 #include <u.h>
2 #include <libc.h>
3 #include <disk.h>
4 #include "dat.h"
5 #include "fns.h"
6 
7 enum
8 {
9 	Pagesz		= 255,
10 };
11 
12 static Dev mmcdev;
13 
14 typedef struct Mmcaux Mmcaux;
15 struct Mmcaux {
16 	uchar page05[Pagesz];
17 	int page05ok;
18 
19 	int pagecmdsz;
20 	ulong mmcnwa;
21 
22 	int nropen;
23 	int nwopen;
24 	long ntotby;
25 	long ntotbk;
26 };
27 
28 static ulong
29 bige(void *p)
30 {
31 	uchar *a;
32 
33 	a = p;
34 	return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
35 }
36 
37 static ushort
38 biges(void *p)
39 {
40 	uchar *a;
41 
42 	a = p;
43 	return (a[0]<<8) | a[1];
44 }
45 
46 static void
47 hexdump(void *v, int n)
48 {
49 	int i;
50 	uchar *p;
51 
52 	p = v;
53 	for(i=0; i<n; i++){
54 		print("%.2ux ", p[i]);
55 		if((i%8) == 7)
56 			print("\n");
57 	}
58 	if(i%8)
59 		print("\n");
60 }
61 
62 static int
63 mmcgetpage10(Drive *drive, int page, void *v)
64 {
65 	uchar cmd[10], resp[512];
66 	int n, r;
67 
68 	memset(cmd, 0, sizeof(cmd));
69 	cmd[0] = 0x5A;
70 	cmd[2] = page;
71 	cmd[8] = 255;
72 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
73 	if(n < 8)
74 		return -1;
75 
76 	r = (resp[6]<<8) | resp[7];
77 	n -= 8+r;
78 
79 	if(n < 0)
80 		return -1;
81 	if(n > Pagesz)
82 		n = Pagesz;
83 
84 	memmove(v, &resp[8+r], n);
85 	return n;
86 }
87 
88 static int
89 mmcgetpage6(Drive *drive, int page, void *v)
90 {
91 	uchar cmd[6], resp[512];
92 	int n;
93 
94 	memset(cmd, 0, sizeof(cmd));
95 	cmd[0] = 0x1A;
96 	cmd[2] = page;
97 	cmd[4] = 255;
98 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
99 	if(n < 4)
100 		return -1;
101 
102 	n -= 4+resp[3];
103 	if(n < 0)
104 		return -1;
105 	if(n > Pagesz)
106 		n = Pagesz;
107 
108 	memmove(v, &resp[4+resp[3]], n);
109 	return n;
110 }
111 
112 static int
113 mmcsetpage10(Drive *drive, int page, void *v)
114 {
115 	uchar cmd[10], *p, *pagedata;
116 	int len, n;
117 
118 	pagedata = v;
119 	assert(pagedata[0] == page);
120 	len = 8+2+pagedata[1];
121 	p = emalloc(len);
122 	memmove(p+8, pagedata, pagedata[1]);
123 	memset(cmd, 0, sizeof(cmd));
124 	cmd[0] = 0x55;
125 	cmd[1] = 0x10;
126 	cmd[8] = len;
127 
128 //	print("cmd\n");
129 //	hexdump(cmd, 10);
130 //	print("page\n");
131 //	hexdump(p, len);
132 
133 	n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite);
134 	free(p);
135 	if(n < len)
136 		return -1;
137 	return 0;
138 }
139 
140 static int
141 mmcsetpage6(Drive *drive, int page, void *v)
142 {
143 	uchar cmd[6], *p, *pagedata;
144 	int len, n;
145 
146 	pagedata = v;
147 	assert(pagedata[0] == page);
148 	len = 4+2+pagedata[1];
149 	p = emalloc(len);
150 	memmove(p+4, pagedata, pagedata[1]);
151 	memset(cmd, 0, sizeof(cmd));
152 	cmd[0] = 0x15;
153 	cmd[1] = 0x10;
154 	cmd[4] = len;
155 
156 	n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite);
157 	free(p);
158 	if(n < len)
159 		return -1;
160 	return 0;
161 }
162 
163 static int
164 mmcgetpage(Drive *drive, int page, void *v)
165 {
166 	Mmcaux *aux;
167 
168 	aux = drive->aux;
169 	switch(aux->pagecmdsz) {
170 	case 10:
171 		return mmcgetpage10(drive, page, v);
172 	case 6:
173 		return mmcgetpage6(drive, page, v);
174 	default:
175 		assert(0);
176 	}
177 	return -1;
178 }
179 
180 static int
181 mmcsetpage(Drive *drive, int page, void *v)
182 {
183 	Mmcaux *aux;
184 
185 	aux = drive->aux;
186 	switch(aux->pagecmdsz) {
187 	case 10:
188 		return mmcsetpage10(drive, page, v);
189 	case 6:
190 		return mmcsetpage6(drive, page, v);
191 	default:
192 		assert(0);
193 	}
194 	return -1;
195 }
196 
197 int
198 mmcstatus(Drive *drive)
199 {
200 	uchar cmd[12];
201 
202 	memset(cmd, 0, sizeof(cmd));
203 	cmd[0] = 0xBD;
204 	return scsi(drive, cmd, sizeof(cmd), nil, 0, Sread);
205 }
206 
207 void
208 mmcgetspeed(Drive *drive)
209 {
210 	int n, maxread, curread, maxwrite, curwrite;
211 	uchar buf[Pagesz];
212 
213 	n = mmcgetpage(drive, 0x2A, buf);
214 	maxread = (buf[8]<<8)|buf[9];
215 	curread = (buf[14]<<8)|buf[15];
216 	maxwrite = (buf[18]<<8)|buf[19];
217 	curwrite = (buf[20]<<8)|buf[21];
218 
219 	if(n < 22 || (maxread && maxread < 170) || (curread && curread < 170))
220 		return;	/* bogus data */
221 
222 	drive->readspeed = curread;
223 	drive->writespeed = curwrite;
224 	drive->maxreadspeed = maxread;
225 	drive->maxwritespeed = maxwrite;
226 }
227 
228 Drive*
229 mmcprobe(Scsi *scsi)
230 {
231 	Mmcaux *aux;
232 	Drive *drive;
233 	uchar buf[Pagesz];
234 	int cap;
235 
236 	/* BUG: confirm mmc better? */
237 
238 	drive = emalloc(sizeof(Drive));
239 	drive->Scsi = *scsi;
240 	drive->Dev = mmcdev;
241 	aux = emalloc(sizeof(Mmcaux));
242 	drive->aux = aux;
243 
244 	/* attempt to read CD capabilities page */
245 	if(mmcgetpage10(drive, 0x2A, buf) >= 0)
246 		aux->pagecmdsz = 10;
247 	else if(mmcgetpage6(drive, 0x2A, buf) >= 0)
248 		aux->pagecmdsz = 6;
249 	else {
250 		werrstr("not an mmc device");
251 		free(drive);
252 		return nil;
253 	}
254 
255 	cap = 0;
256 	if(buf[3] & 3)	/* 2=cdrw, 1=cdr */
257 		cap |= Cwrite;
258 	if(buf[5] & 1)
259 		cap |= Ccdda;
260 
261 //	print("read %d max %d\n", biges(buf+14), biges(buf+8));
262 //	print("write %d max %d\n", biges(buf+20), biges(buf+18));
263 
264 	/* cache page 05 (write parameter page) */
265 	if((cap & Cwrite) && mmcgetpage(drive, 0x05, aux->page05) >= 0)
266 		aux->page05ok = 1;
267 	else
268 		cap &= ~Cwrite;
269 
270 	drive->cap = cap;
271 
272 	mmcgetspeed(drive);
273 	return drive;
274 }
275 
276 static int
277 mmctrackinfo(Drive *drive, int t, int i)
278 {
279 	uchar cmd[10], resp[255];
280 	int n, type, bs;
281 	uchar tmode;
282 	ulong beg, size;
283 	Mmcaux *aux;
284 
285 	aux = drive->aux;
286 	memset(cmd, 0, sizeof(cmd));
287 	cmd[0] = 0x52;	/* get track info */
288 	cmd[1] = 1;
289 	cmd[2] = t>>24;
290 	cmd[3] = t>>16;
291 	cmd[4] = t>>8;
292 	cmd[5] = t;
293 	cmd[7] = sizeof(resp)>>8;
294 	cmd[8] = sizeof(resp);
295 	n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread);
296 	if(n < 28) {
297 		if(vflag)
298 			print("trackinfo %d fails n=%d %r\n", t, n);
299 		return -1;
300 	}
301 
302 	beg = bige(&resp[8]);
303 	size = bige(&resp[24]);
304 
305 	tmode = resp[5] & 0x0D;
306 //	dmode = resp[6] & 0x0F;
307 
308 	if(vflag)
309 		print("track %d type 0x%x\n", t, tmode);
310 	type = TypeNone;
311 	bs = BScdda;
312 	switch(tmode){
313 	case 0:
314 		type = TypeAudio;
315 		bs = BScdda;
316 		break;
317 	case 1:	/* 2 audio channels, with pre-emphasis 50/15 μs */
318 		if(vflag)
319 			print("audio channels with preemphasis on track %d (u%.3d)\n", t, i);
320 		type = TypeNone;
321 		break;
322 	case 4:	/* data track, recorded uninterrupted */
323 		type = TypeData;
324 		bs = BScdrom;
325 		break;
326 	case 5:	/* data track, recorded interrupted */
327 	default:
328 		if(vflag)
329 			print("unknown track type %d\n", tmode);
330 	}
331 
332 	drive->track[i].mtime = drive->changetime;
333 	drive->track[i].beg = beg;
334 	drive->track[i].end = beg+size;
335 	drive->track[i].type = type;
336 	drive->track[i].bs = bs;
337 	drive->track[i].size = (vlong)(size-2)*bs;	/* -2: skip lead out */
338 
339 	if(resp[6] & (1<<6)) {
340 		drive->track[i].type = TypeBlank;
341 		drive->writeok = 1;
342 	}
343 
344 	if(t == 0xFF)
345 		aux->mmcnwa = bige(&resp[12]);
346 
347 	return 0;
348 }
349 
350 static int
351 mmcreadtoc(Drive *drive, int type, int track, void *data, int nbytes)
352 {
353 	uchar cmd[10];
354 
355 	memset(cmd, 0, sizeof(cmd));
356 	cmd[0] = 0x43;
357 	cmd[1] = type;
358 	cmd[6] = track;
359 	cmd[7] = nbytes>>8;
360 	cmd[8] = nbytes;
361 
362 	return scsi(drive, cmd, sizeof(cmd), data, nbytes, Sread);
363 }
364 
365 static int
366 mmcreaddiscinfo(Drive *drive, void *data, int nbytes)
367 {
368 	uchar cmd[10];
369 	int n;
370 
371 	memset(cmd, 0, sizeof(cmd));
372 	cmd[0] = 0x51;
373 	cmd[7] = nbytes>>8;
374 	cmd[8] = nbytes;
375 	n = scsi(drive, cmd, sizeof(cmd), data, nbytes, Sread);
376 	if(n < 24) {
377 		if(n >= 0)
378 			werrstr("rdiscinfo returns %d", n);
379 		return -1;
380 	}
381 
382 	return n;
383 }
384 
385 static Msf
386 rdmsf(uchar *p)
387 {
388 	Msf msf;
389 
390 	msf.m = p[0];
391 	msf.s = p[1];
392 	msf.f = p[2];
393 	return msf;
394 }
395 
396 static int
397 mmcgettoc(Drive *drive)
398 {
399 	uchar resp[1024];
400 	int i, n, first, last;
401 	ulong tot;
402 	Track *t;
403 
404 	/*
405 	 * if someone has swapped the cd,
406 	 * mmcreadtoc will get ``medium changed'' and the
407 	 * scsi routines will set nchange and changetime in the
408 	 * scsi device.
409 	 */
410 	mmcreadtoc(drive, 0, 0, resp, sizeof(resp));
411 	if(drive->Scsi.changetime == 0) {	/* no media present */
412 		drive->ntrack = 0;
413 		return 0;
414 	}
415 
416 	if(drive->nchange == drive->Scsi.nchange && drive->changetime != 0)
417 		return 0;
418 
419 	drive->ntrack = 0;
420 	drive->nameok = 0;
421 	drive->nchange = drive->Scsi.nchange;
422 	drive->changetime = drive->Scsi.changetime;
423 	drive->writeok = 0;
424 
425 	for(i=0; i<nelem(drive->track); i++){
426 		memset(&drive->track[i].mbeg, 0, sizeof(Msf));
427 		memset(&drive->track[i].mend, 0, sizeof(Msf));
428 	}
429 
430 	/*
431 	 * find number of tracks
432 	 */
433 	if((n=mmcreadtoc(drive, 0x02, 0, resp, sizeof(resp))) < 4) {
434 		/*
435 		 * on a blank disc in a cd-rw, use readdiscinfo
436 		 * to find the track info.
437 		 */
438 		if(mmcreaddiscinfo(drive, resp, sizeof(resp)) < 0)
439 			return -1;
440 		if(resp[4] != 1)
441 			print("multi-session disc %d\n", resp[4]);
442 		first = resp[3];
443 		last = resp[6];
444 		if(vflag)
445 			print("blank disc %d %d\n", first, last);
446 		drive->writeok = 1;
447 	} else {
448 		first = resp[2];
449 		last = resp[3];
450 
451 		if(n >= 4+8*(last-first+2)) {
452 			for(i=0; i<=last-first+1; i++)	/* <=: track[last-first+1] = end */
453 				drive->track[i].mbeg = rdmsf(resp+4+i*8+5);
454 			for(i=0; i<last-first+1; i++)
455 				drive->track[i].mend = drive->track[i+1].mbeg;
456 		}
457 	}
458 
459 	if(vflag)
460 		print("first %d last %d\n", first, last);
461 
462 	if(first == 0 && last == 0)
463 		first = 1;
464 
465 	if(first <= 0 || first >= Maxtrack) {
466 		werrstr("first table %d not in range", first);
467 		return -1;
468 	}
469 	if(last <= 0 || last >= Maxtrack) {
470 		werrstr("last table %d not in range", last);
471 		return -1;
472 	}
473 
474 	if(drive->cap & Cwrite) {	/* CDR drives are easy */
475 		for(i = first; i <= last; i++)
476 			mmctrackinfo(drive, i, i-first);
477 	} else {
478 		/*
479 		 * otherwise we need to infer endings from the
480 		 * beginnings of other tracks.
481 		 */
482 		for(i = first; i <= last; i++) {
483 			memset(resp, 0, sizeof(resp));
484 			if(mmcreadtoc(drive, 0x00, i, resp, sizeof(resp)) < 0)
485 				break;
486 			t = &drive->track[i-first];
487 			t->mtime = drive->changetime;
488 			t->type = TypeData;
489 			t->bs = BScdrom;
490 			t->beg = bige(resp+8);
491 			if(!(resp[5] & 4)) {
492 				t->type = TypeAudio;
493 				t->bs = BScdda;
494 			}
495 		}
496 
497 		if((long)drive->track[0].beg < 0)	/* i've seen negative track 0's */
498 			drive->track[0].beg = 0;
499 
500 		tot = 0;
501 		memset(resp, 0, sizeof(resp));
502 		if(mmcreadtoc(drive, 0x00, 0xAA, resp, sizeof(resp)) < 0)
503 			print("bad\n");
504 		if(resp[6])
505 			tot = bige(resp+8);
506 
507 		for(i=last; i>=first; i--) {
508 			t = &drive->track[i-first];
509 			t->end = tot;
510 			tot = t->beg;
511 			if(t->end <= t->beg) {
512 				t->beg = 0;
513 				t->end = 0;
514 			}
515 			t->size = (t->end - t->beg - 2) * (vlong)t->bs;	/* -2: skip lead out */
516 		}
517 	}
518 
519 	drive->firsttrack = first;
520 	drive->ntrack = last+1-first;
521 	return 0;
522 }
523 
524 static int
525 mmcsetbs(Drive *drive, int bs)
526 {
527 	uchar *p;
528 	Mmcaux *aux;
529 
530 	aux = drive->aux;
531 
532 	assert(aux->page05ok);
533 
534 	p = aux->page05;
535 	p[2] = 0x01;				/* track-at-once */
536 //	if(xflag)
537 //		p[2] |= 0x10;			/* test-write */
538 
539 	switch(bs){
540 	case BScdrom:
541 		p[3] = (p[3] & ~0x07)|0x04;	/* data track, uninterrupted */
542 		p[4] = 0x08;			/* mode 1 CD-ROM */
543 		p[8] = 0;			/* session format CD-DA or CD-ROM */
544 		break;
545 
546 	case BScdda:
547 		p[3] = (p[3] & ~0x07)|0x00;	/* 2 audio channels without pre-emphasis */
548 		p[4] = 0x00;			/* raw data */
549 		p[8] = 0;			/* session format CD-DA or CD-ROM */
550 		break;
551 
552 	case BScdxa:
553 		p[3] = (p[3] & ~0x07)|0x04;	/* data track, uninterrupted */
554 		p[4] = 0x09;			/* mode 2 */
555 		p[8] = 0x20;			/* session format CD-ROM XA */
556 		break;
557 
558 	default:
559 		assert(0);
560 	}
561 
562 	if(mmcsetpage(drive, 0x05, p) < 0)
563 		return -1;
564 }
565 
566 static long
567 mmcread(Buf *buf, void *v, long nblock, long off)
568 {
569 	Drive *drive;
570 	int bs;
571 	uchar cmd[12];
572 	long n, nn;
573 	Otrack *o;
574 
575 	o = buf->otrack;
576 	drive = o->drive;
577 	bs = o->track->bs;
578 	off += o->track->beg;
579 
580 	if(nblock >= (1<<10)) {
581 		werrstr("mmcread too big");
582 		if(vflag)
583 			fprint(2, "mmcread too big\n");
584 		return -1;
585 	}
586 
587 	/* truncate nblock modulo size of track */
588 	if(off > o->track->end - 2) {
589 		werrstr("read past end of track");
590 		if(vflag)
591 			fprint(2, "end of track (%ld->%ld off %ld)", o->track->beg, o->track->end-2, off);
592 		return -1;
593 	}
594 	if(off == o->track->end - 2)
595 		return 0;
596 
597 	if(off+nblock > o->track->end - 2)
598 		nblock = o->track->end - 2 - off;
599 
600 	memset(cmd, 0, sizeof(cmd));
601 	cmd[0] = 0xBE;
602 	cmd[2] = off>>24;
603 	cmd[3] = off>>16;
604 	cmd[4] = off>>8;
605 	cmd[5] = off>>0;
606 	cmd[6] = nblock>>16;
607 	cmd[7] = nblock>>8;
608 	cmd[8] = nblock>>0;
609 	cmd[9] = 0x10;
610 
611 	switch(bs){
612 	case BScdda:
613 		cmd[1] = 0x04;
614 		break;
615 
616 	case BScdrom:
617 		cmd[1] = 0x08;
618 		break;
619 
620 	case BScdxa:
621 		cmd[1] = 0x0C;
622 		break;
623 
624 	default:
625 		werrstr("unknown bs %d", bs);
626 		return -1;
627 	}
628 
629 	n = nblock*bs;
630 	nn = scsi(drive, cmd, sizeof(cmd), v, n, Sread);
631 	if(nn != n) {
632 		werrstr("short read %ld/%ld", nn, n);
633 		if(vflag)
634 			print("read off %lud nblock %ld bs %d failed\n", off, nblock, bs);
635 		return -1;
636 	}
637 
638 	return nblock;
639 }
640 
641 static Otrack*
642 mmcopenrd(Drive *drive, int trackno)
643 {
644 	Otrack *o;
645 	Mmcaux *aux;
646 
647 	if(trackno < 0 || trackno >= drive->ntrack) {
648 		werrstr("track number out of range");
649 		return nil;
650 	}
651 
652 	aux = drive->aux;
653 	if(aux->nwopen) {
654 		werrstr("disk in use for writing");
655 		return nil;
656 	}
657 
658 	o = emalloc(sizeof(Otrack));
659 	o->drive = drive;
660 	o->track = &drive->track[trackno];
661 	o->nchange = drive->nchange;
662 	o->omode = OREAD;
663 	o->buf = bopen(mmcread, OREAD, o->track->bs, Nblock);
664 	o->buf->otrack = o;
665 
666 	aux->nropen++;
667 
668 	return o;
669 }
670 
671 static long
672 mmcxwrite(Otrack *o, void *v, long nblk)
673 {
674 	uchar cmd[10];
675 	Mmcaux *aux;
676 
677 	assert(o->omode == OWRITE);
678 
679 	aux = o->drive->aux;
680 	aux->ntotby += nblk*o->track->bs;
681 	aux->ntotbk += nblk;
682 	memset(cmd, 0, sizeof(cmd));
683 	cmd[0] = 0x2a;	/* write */
684 	cmd[2] = aux->mmcnwa>>24;
685 	cmd[3] = aux->mmcnwa>>16;
686 	cmd[4] = aux->mmcnwa>>8;
687 	cmd[5] = aux->mmcnwa;
688 	cmd[7] = nblk>>8;
689 	cmd[8] = nblk>>0;
690 	if(vflag)
691 		print("%lld: write %ld at 0x%lux\n", nsec(), nblk, aux->mmcnwa);
692 	aux->mmcnwa += nblk;
693 	return scsi(o->drive, cmd, sizeof(cmd), v, nblk*o->track->bs, Swrite);
694 }
695 
696 static long
697 mmcwrite(Buf *buf, void *v, long nblk, long)
698 {
699 	return mmcxwrite(buf->otrack, v, nblk);
700 }
701 
702 static Otrack*
703 mmccreate(Drive *drive, int type)
704 {
705 	int bs;
706 	Mmcaux *aux;
707 	Track *t;
708 	Otrack *o;
709 
710 	aux = drive->aux;
711 
712 	if(aux->nropen || aux->nwopen) {
713 		werrstr("drive in use");
714 		return nil;
715 	}
716 
717 	switch(type){
718 	case TypeAudio:
719 		bs = BScdda;
720 		break;
721 	case TypeData:
722 		bs = BScdrom;
723 		break;
724 	default:
725 		werrstr("bad type %d", type);
726 		return nil;
727 	}
728 
729 	if(mmctrackinfo(drive, 0xFF, Maxtrack)) {		/* the invisible track */
730 		werrstr("CD not writable");
731 		return nil;
732 	}
733 	if(mmcsetbs(drive, bs) < 0) {
734 		werrstr("cannot set bs mode");
735 		return nil;
736 	}
737 	if(mmctrackinfo(drive, 0xFF, Maxtrack)) {		/* the invisible track */
738 		werrstr("CD not writable 2");
739 		return nil;
740 	}
741 
742 	aux->ntotby = 0;
743 	aux->ntotbk = 0;
744 
745 	t = &drive->track[drive->ntrack++];
746 	t->size = 0;
747 	t->bs = bs;
748 	t->beg = aux->mmcnwa;
749 	t->end = 0;
750 	t->type = type;
751 	drive->nameok = 0;
752 
753 	o = emalloc(sizeof(Otrack));
754 	o->drive = drive;
755 	o->nchange = drive->nchange;
756 	o->omode = OWRITE;
757 	o->track = t;
758 	o->buf = bopen(mmcwrite, OWRITE, bs, Nblock);
759 	o->buf->otrack = o;
760 
761 	aux->nwopen++;
762 
763 	if(vflag)
764 		print("mmcinit: nwa = 0x%luX\n", aux->mmcnwa);
765 
766 	return o;
767 }
768 
769 void
770 mmcsynccache(Drive *drive)
771 {
772 	uchar cmd[10];
773 	Mmcaux *aux;
774 
775 	memset(cmd, 0, sizeof(cmd));
776 	cmd[0] = 0x35;	/* flush */
777 	scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
778 	if(vflag) {
779 		aux = drive->aux;
780 		print("mmcsynccache: bytes = %ld blocks = %ld, mmcnwa 0x%luX\n",
781 			aux->ntotby, aux->ntotbk, aux->mmcnwa);
782 	}
783 /* rsc: seems not to work on some drives; 	mmcclose(1, 0xFF); */
784 }
785 
786 static void
787 mmcclose(Otrack *o)
788 {
789 	Mmcaux *aux;
790 	static uchar zero[2*BSmax];
791 
792 	aux = o->drive->aux;
793 	if(o->omode == OREAD)
794 		aux->nropen--;
795 	else if(o->omode == OWRITE) {
796 		aux->nwopen--;
797 		mmcxwrite(o, zero, 2);	/* write lead out */
798 		mmcsynccache(o->drive);
799 		o->drive->nchange = -1;	/* force reread toc */
800 	}
801 	free(o);
802 }
803 
804 static int
805 mmcxclose(Drive *drive, int ts, int trackno)
806 {
807 	uchar cmd[10];
808 
809 	/*
810 	 * ts: 1 == track, 2 == session
811 	 */
812 	memset(cmd, 0, sizeof(cmd));
813 	cmd[0] = 0x5B;
814 	cmd[2] = ts;
815 	if(ts == 1)
816 		cmd[5] = trackno;
817 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
818 }
819 
820 static int
821 mmcfixate(Drive *drive)
822 {
823 	uchar *p;
824 	Mmcaux *aux;
825 
826 	if((drive->cap & Cwrite) == 0) {
827 		werrstr("not a writer");
828 		return -1;
829 	}
830 
831 	drive->nchange = -1;	/* force reread toc */
832 
833 	aux = drive->aux;
834 	p = aux->page05;
835 	p[3] = (p[3] & ~0xC0);
836 	if(mmcsetpage(drive, 0x05, p) < 0)
837 		return -1;
838 
839 /* rsc: seems not to work on some drives; 	mmcclose(1, 0xFF); */
840 	return mmcxclose(drive, 0x02, 0);
841 }
842 
843 static int
844 mmcsession(Drive *drive)
845 {
846 	uchar *p;
847 	Mmcaux *aux;
848 
849 	drive->nchange = -1;	/* force reread toc */
850 
851 	aux = drive->aux;
852 	p = aux->page05;
853 	p[3] = (p[3] & ~0xC0);
854 	if(mmcsetpage(drive, 0x05, p) < 0)
855 		return -1;
856 
857 /* rsc: seems not to work on some drives; 	mmcclose(1, 0xFF); */
858 	return mmcxclose(drive, 0x02, 0);
859 }
860 
861 static int
862 mmcblank(Drive *drive, int quick)
863 {
864 	uchar cmd[12];
865 
866 	drive->nchange = -1;	/* force reread toc */
867 
868 	memset(cmd, 0, sizeof(cmd));
869 	cmd[0] = 0xA1;	/* blank */
870 	/* cmd[1] = 0 means blank the whole disc; = 1 just the header */
871 	cmd[1] = quick ? 0x01 : 0x00;
872 
873 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
874 }
875 
876 static int
877 start(Drive *drive, int code)
878 {
879 	uchar cmd[6];
880 
881 	memset(cmd, 0, sizeof(cmd));
882 	cmd[0] = 0x1B;
883 	cmd[4] = code;
884 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
885 }
886 
887 static char*
888 e(int status)
889 {
890 	if(status < 0)
891 		return geterrstr();
892 	return nil;
893 }
894 
895 static char*
896 mmcctl(Drive *drive, int argc, char **argv)
897 {
898 	if(argc < 1)
899 		return nil;
900 
901 	if(strcmp(argv[0], "blank") == 0)
902 		return e(mmcblank(drive, 0));
903 	if(strcmp(argv[0], "quickblank") == 0)
904 		return e(mmcblank(drive, 1));
905 	if(strcmp(argv[0], "eject") == 0)
906 		return e(start(drive, 2));
907 	if(strcmp(argv[0], "ingest") == 0)
908 		return e(start(drive, 3));
909 	return "bad arg";
910 }
911 
912 static char*
913 mmcsetspeed(Drive *drive, int r, int w)
914 {
915 	char *rv;
916 	uchar cmd[12];
917 
918 	memset(cmd, 0, sizeof(cmd));
919 	cmd[0] = 0xBB;
920 	cmd[2] = r>>8;
921 	cmd[3] = r;
922 	cmd[4] = w>>8;
923 	cmd[5] = w;
924 	rv = e(scsi(drive, cmd, sizeof(cmd), nil, 0, Snone));
925 	mmcgetspeed(drive);
926 	return rv;
927 }
928 
929 static Dev mmcdev = {
930 	mmcopenrd,
931 	mmccreate,
932 	bufread,
933 	bufwrite,
934 	mmcclose,
935 	mmcgettoc,
936 	mmcfixate,
937 	mmcctl,
938 	mmcsetspeed,
939 };
940