xref: /plan9-contrib/sys/src/cmd/cdfs/mmc.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
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 	return 0;
565 }
566 
567 static long
568 mmcread(Buf *buf, void *v, long nblock, long off)
569 {
570 	Drive *drive;
571 	int bs;
572 	uchar cmd[12];
573 	long n, nn;
574 	Otrack *o;
575 
576 	o = buf->otrack;
577 	drive = o->drive;
578 	bs = o->track->bs;
579 	off += o->track->beg;
580 
581 	if(nblock >= (1<<10)) {
582 		werrstr("mmcread too big");
583 		if(vflag)
584 			fprint(2, "mmcread too big\n");
585 		return -1;
586 	}
587 
588 	/* truncate nblock modulo size of track */
589 	if(off > o->track->end - 2) {
590 		werrstr("read past end of track");
591 		if(vflag)
592 			fprint(2, "end of track (%ld->%ld off %ld)", o->track->beg, o->track->end-2, off);
593 		return -1;
594 	}
595 	if(off == o->track->end - 2)
596 		return 0;
597 
598 	if(off+nblock > o->track->end - 2)
599 		nblock = o->track->end - 2 - off;
600 
601 	memset(cmd, 0, sizeof(cmd));
602 	cmd[0] = 0xBE;
603 	cmd[2] = off>>24;
604 	cmd[3] = off>>16;
605 	cmd[4] = off>>8;
606 	cmd[5] = off>>0;
607 	cmd[6] = nblock>>16;
608 	cmd[7] = nblock>>8;
609 	cmd[8] = nblock>>0;
610 	cmd[9] = 0x10;
611 
612 	switch(bs){
613 	case BScdda:
614 		cmd[1] = 0x04;
615 		break;
616 
617 	case BScdrom:
618 		cmd[1] = 0x08;
619 		break;
620 
621 	case BScdxa:
622 		cmd[1] = 0x0C;
623 		break;
624 
625 	default:
626 		werrstr("unknown bs %d", bs);
627 		return -1;
628 	}
629 
630 	n = nblock*bs;
631 	nn = scsi(drive, cmd, sizeof(cmd), v, n, Sread);
632 	if(nn != n) {
633 		if(nn != -1)
634 			werrstr("short read %ld/%ld", nn, n);
635 		if(vflag)
636 			print("read off %lud nblock %ld bs %d failed\n", off, nblock, bs);
637 		return -1;
638 	}
639 
640 	return nblock;
641 }
642 
643 static Otrack*
644 mmcopenrd(Drive *drive, int trackno)
645 {
646 	Otrack *o;
647 	Mmcaux *aux;
648 
649 	if(trackno < 0 || trackno >= drive->ntrack) {
650 		werrstr("track number out of range");
651 		return nil;
652 	}
653 
654 	aux = drive->aux;
655 	if(aux->nwopen) {
656 		werrstr("disk in use for writing");
657 		return nil;
658 	}
659 
660 	o = emalloc(sizeof(Otrack));
661 	o->drive = drive;
662 	o->track = &drive->track[trackno];
663 	o->nchange = drive->nchange;
664 	o->omode = OREAD;
665 	o->buf = bopen(mmcread, OREAD, o->track->bs, Nblock);
666 	o->buf->otrack = o;
667 
668 	aux->nropen++;
669 
670 	return o;
671 }
672 
673 static long
674 mmcxwrite(Otrack *o, void *v, long nblk)
675 {
676 	uchar cmd[10];
677 	Mmcaux *aux;
678 
679 	assert(o->omode == OWRITE);
680 
681 	aux = o->drive->aux;
682 	aux->ntotby += nblk*o->track->bs;
683 	aux->ntotbk += nblk;
684 	memset(cmd, 0, sizeof(cmd));
685 	cmd[0] = 0x2a;	/* write */
686 	cmd[2] = aux->mmcnwa>>24;
687 	cmd[3] = aux->mmcnwa>>16;
688 	cmd[4] = aux->mmcnwa>>8;
689 	cmd[5] = aux->mmcnwa;
690 	cmd[7] = nblk>>8;
691 	cmd[8] = nblk>>0;
692 	if(vflag)
693 		print("%lld: write %ld at 0x%lux\n", nsec(), nblk, aux->mmcnwa);
694 	aux->mmcnwa += nblk;
695 	return scsi(o->drive, cmd, sizeof(cmd), v, nblk*o->track->bs, Swrite);
696 }
697 
698 static long
699 mmcwrite(Buf *buf, void *v, long nblk, long)
700 {
701 	return mmcxwrite(buf->otrack, v, nblk);
702 }
703 
704 static Otrack*
705 mmccreate(Drive *drive, int type)
706 {
707 	int bs;
708 	Mmcaux *aux;
709 	Track *t;
710 	Otrack *o;
711 
712 	aux = drive->aux;
713 
714 	if(aux->nropen || aux->nwopen) {
715 		werrstr("drive in use");
716 		return nil;
717 	}
718 
719 	switch(type){
720 	case TypeAudio:
721 		bs = BScdda;
722 		break;
723 	case TypeData:
724 		bs = BScdrom;
725 		break;
726 	default:
727 		werrstr("bad type %d", type);
728 		return nil;
729 	}
730 
731 	if(mmctrackinfo(drive, 0xFF, Maxtrack)) {		/* the invisible track */
732 		werrstr("CD not writable");
733 		return nil;
734 	}
735 	if(mmcsetbs(drive, bs) < 0) {
736 		werrstr("cannot set bs mode");
737 		return nil;
738 	}
739 	if(mmctrackinfo(drive, 0xFF, Maxtrack)) {		/* the invisible track */
740 		werrstr("CD not writable 2");
741 		return nil;
742 	}
743 
744 	aux->ntotby = 0;
745 	aux->ntotbk = 0;
746 
747 	t = &drive->track[drive->ntrack++];
748 	t->size = 0;
749 	t->bs = bs;
750 	t->beg = aux->mmcnwa;
751 	t->end = 0;
752 	t->type = type;
753 	drive->nameok = 0;
754 
755 	o = emalloc(sizeof(Otrack));
756 	o->drive = drive;
757 	o->nchange = drive->nchange;
758 	o->omode = OWRITE;
759 	o->track = t;
760 	o->buf = bopen(mmcwrite, OWRITE, bs, Nblock);
761 	o->buf->otrack = o;
762 
763 	aux->nwopen++;
764 
765 	if(vflag)
766 		print("mmcinit: nwa = 0x%luX\n", aux->mmcnwa);
767 
768 	return o;
769 }
770 
771 void
772 mmcsynccache(Drive *drive)
773 {
774 	uchar cmd[10];
775 	Mmcaux *aux;
776 
777 	memset(cmd, 0, sizeof(cmd));
778 	cmd[0] = 0x35;	/* flush */
779 	scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
780 	if(vflag) {
781 		aux = drive->aux;
782 		print("mmcsynccache: bytes = %ld blocks = %ld, mmcnwa 0x%luX\n",
783 			aux->ntotby, aux->ntotbk, aux->mmcnwa);
784 	}
785 /* rsc: seems not to work on some drives; 	mmcclose(1, 0xFF); */
786 }
787 
788 static void
789 mmcclose(Otrack *o)
790 {
791 	Mmcaux *aux;
792 	static uchar zero[2*BSmax];
793 
794 	aux = o->drive->aux;
795 	if(o->omode == OREAD)
796 		aux->nropen--;
797 	else if(o->omode == OWRITE) {
798 		aux->nwopen--;
799 		mmcxwrite(o, zero, 2);	/* write lead out */
800 		mmcsynccache(o->drive);
801 		o->drive->nchange = -1;	/* force reread toc */
802 	}
803 	free(o);
804 }
805 
806 static int
807 mmcxclose(Drive *drive, int ts, int trackno)
808 {
809 	uchar cmd[10];
810 
811 	/*
812 	 * ts: 1 == track, 2 == session
813 	 */
814 	memset(cmd, 0, sizeof(cmd));
815 	cmd[0] = 0x5B;
816 	cmd[2] = ts;
817 	if(ts == 1)
818 		cmd[5] = trackno;
819 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
820 }
821 
822 static int
823 mmcfixate(Drive *drive)
824 {
825 	uchar *p;
826 	Mmcaux *aux;
827 
828 	if((drive->cap & Cwrite) == 0) {
829 		werrstr("not a writer");
830 		return -1;
831 	}
832 
833 	drive->nchange = -1;	/* force reread toc */
834 
835 	aux = drive->aux;
836 	p = aux->page05;
837 	p[3] = (p[3] & ~0xC0);
838 	if(mmcsetpage(drive, 0x05, p) < 0)
839 		return -1;
840 
841 /* rsc: seems not to work on some drives; 	mmcclose(1, 0xFF); */
842 	return mmcxclose(drive, 0x02, 0);
843 }
844 
845 static int
846 mmcsession(Drive *drive)
847 {
848 	uchar *p;
849 	Mmcaux *aux;
850 
851 	drive->nchange = -1;	/* force reread toc */
852 
853 	aux = drive->aux;
854 	p = aux->page05;
855 	p[3] = (p[3] & ~0xC0);
856 	if(mmcsetpage(drive, 0x05, p) < 0)
857 		return -1;
858 
859 /* rsc: seems not to work on some drives; 	mmcclose(1, 0xFF); */
860 	return mmcxclose(drive, 0x02, 0);
861 }
862 
863 static int
864 mmcblank(Drive *drive, int quick)
865 {
866 	uchar cmd[12];
867 
868 	drive->nchange = -1;	/* force reread toc */
869 
870 	memset(cmd, 0, sizeof(cmd));
871 	cmd[0] = 0xA1;	/* blank */
872 	/* cmd[1] = 0 means blank the whole disc; = 1 just the header */
873 	cmd[1] = quick ? 0x01 : 0x00;
874 
875 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
876 }
877 
878 static int
879 start(Drive *drive, int code)
880 {
881 	uchar cmd[6];
882 
883 	memset(cmd, 0, sizeof(cmd));
884 	cmd[0] = 0x1B;
885 	cmd[4] = code;
886 	return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);
887 }
888 
889 static char*
890 e(int status)
891 {
892 	if(status < 0)
893 		return geterrstr();
894 	return nil;
895 }
896 
897 static char*
898 mmcctl(Drive *drive, int argc, char **argv)
899 {
900 	if(argc < 1)
901 		return nil;
902 
903 	if(strcmp(argv[0], "blank") == 0)
904 		return e(mmcblank(drive, 0));
905 	if(strcmp(argv[0], "quickblank") == 0)
906 		return e(mmcblank(drive, 1));
907 	if(strcmp(argv[0], "eject") == 0)
908 		return e(start(drive, 2));
909 	if(strcmp(argv[0], "ingest") == 0)
910 		return e(start(drive, 3));
911 	return "bad arg";
912 }
913 
914 static char*
915 mmcsetspeed(Drive *drive, int r, int w)
916 {
917 	char *rv;
918 	uchar cmd[12];
919 
920 	memset(cmd, 0, sizeof(cmd));
921 	cmd[0] = 0xBB;
922 	cmd[2] = r>>8;
923 	cmd[3] = r;
924 	cmd[4] = w>>8;
925 	cmd[5] = w;
926 	rv = e(scsi(drive, cmd, sizeof(cmd), nil, 0, Snone));
927 	mmcgetspeed(drive);
928 	return rv;
929 }
930 
931 static Dev mmcdev = {
932 	mmcopenrd,
933 	mmccreate,
934 	bufread,
935 	bufwrite,
936 	mmcclose,
937 	mmcgettoc,
938 	mmcfixate,
939 	mmcctl,
940 	mmcsetspeed,
941 };
942