xref: /plan9/sys/src/cmd/usb/disk/disk.c (revision 14dd39c1d685049bb1c596d88db256ffe72f42bd)
1 /*
2  * usb/disk - usb mass storage file server
3  * BUG: supports only the scsi command interface.
4  * BUG: This should use /dev/sdfile to
5  * use the kernel ether device code.
6  */
7 
8 #include <u.h>
9 #include <libc.h>
10 #include <ctype.h>
11 #include <fcall.h>
12 #include <thread.h>
13 #include "scsireq.h"
14 #include "usb.h"
15 #include "usbfs.h"
16 #include "ums.h"
17 
18 enum
19 {
20 	Qdir = 0,
21 	Qctl,
22 	Qraw,
23 	Qdata,
24 	Qmax,
25 };
26 
27 typedef struct Dirtab Dirtab;
28 struct Dirtab
29 {
30 	char	*name;
31 	int	mode;
32 };
33 
34 static Dirtab dirtab[] =
35 {
36 	[Qdir]	"/",	DMDIR|0555,
37 	[Qctl]	"ctl",	0444,
38 	[Qraw]	"raw",	0640,
39 	[Qdata]	"data",	0640,
40 };
41 
42 /*
43  * These are used by scuzz scsireq
44  */
45 int exabyte, force6bytecmds;
46 long maxiosize = MaxIOsize;
47 
48 static int diskdebug;
49 
50 static int
51 getmaxlun(Dev *dev)
52 {
53 	uchar max;
54 	int r;
55 
56 	max = 0;
57 	r = Rd2h|Rclass|Riface;
58 	if(usbcmd(dev, r, Getmaxlun, 0, 0, &max, 1) < 0){
59 		dprint(2, "disk: %s: getmaxlun failed: %r\n", dev->dir);
60 	}else{
61 		max &= 017;			/* 15 is the max. allowed */
62 		dprint(2, "disk: %s: maxlun %d\n", dev->dir, max);
63 	}
64 	return max;
65 }
66 
67 static int
68 umsreset(Ums *ums)
69 {
70 	int r;
71 
72 	r = Rh2d|Rclass|Riface;
73 	if(usbcmd(ums->dev, r, Umsreset, 0, 0, nil, 0) < 0){
74 		fprint(2, "disk: reset: %r\n");
75 		return -1;
76 	}
77 	return 0;
78 }
79 
80 static int
81 umsrecover(Ums *ums)
82 {
83 	if(umsreset(ums) < 0)
84 		return -1;
85 	if(unstall(ums->dev, ums->epin, Ein) < 0)
86 		dprint(2, "disk: unstall epin: %r\n");
87 
88 	/* do we need this when epin == epout? */
89 	if(unstall(ums->dev, ums->epout, Eout) < 0)
90 		dprint(2, "disk: unstall epout: %r\n");
91 	return 0;
92 }
93 
94 static void
95 umsfatal(Ums *ums)
96 {
97 	int i;
98 
99 	devctl(ums->dev, "detach");
100 	for(i = 0; i < ums->maxlun; i++)
101 		usbfsdel(&ums->lun[i].fs);
102 }
103 
104 static int
105 umscapacity(Umsc *lun)
106 {
107 	uchar data[32];
108 
109 	lun->blocks = 0;
110 	lun->capacity = 0;
111 	lun->lbsize = 0;
112 	if(SRrcapacity(lun, data) < 0 && SRrcapacity(lun, data)  < 0)
113 		return -1;
114 	lun->blocks = GETBELONG(data);
115 	lun->lbsize = GETBELONG(data+4);
116 	if(lun->blocks == 0xFFFFFFFF){
117 		if(SRrcapacity16(lun, data) < 0){
118 			lun->lbsize = 0;
119 			lun->blocks = 0;
120 			return -1;
121 		}else{
122 			lun->lbsize = GETBELONG(data + 8);
123 			lun->blocks = (uvlong)GETBELONG(data)<<32 | GETBELONG(data + 4);
124 		}
125 	}
126 	lun->blocks++; /* SRcapacity returns LBA of last block */
127 	lun->capacity = (vlong)lun->blocks * lun->lbsize;
128 	return 0;
129 }
130 
131 static int
132 umsinit(Ums *ums)
133 {
134 	uchar i;
135 	Umsc *lun;
136 	int some;
137 
138 	umsreset(ums);
139 	ums->maxlun = getmaxlun(ums->dev);
140 	ums->lun = mallocz((ums->maxlun+1) * sizeof(*ums->lun), 1);
141 	some = 0;
142 	for(i = 0; i <= ums->maxlun; i++){
143 		lun = &ums->lun[i];
144 		lun->ums = ums;
145 		lun->umsc = lun;
146 		lun->lun = i;
147 		lun->flags = Fopen | Fusb | Frw10;
148 		if(SRinquiry(lun) < 0 && SRinquiry(lun) < 0)
149 			continue;
150 		if(lun->inquiry[0] != 0x00){
151 			/* not a disk */
152 			fprint(2, "%s: lun %d is not a disk (type %#02x)\n",
153 				argv0, i, lun->inquiry[0]);
154 			continue;
155 		}
156 		SRstart(lun, 1);
157 		/*
158 		 * we ignore the device type reported by inquiry.
159 		 * Some devices return a wrong value but would still work.
160 		 */
161 		some++;
162 		lun->inq = smprint("%.48s", (char *)lun->inquiry+8);
163 		umscapacity(lun);
164 	}
165 	if(some == 0){
166 		devctl(ums->dev, "detach");
167 		return -1;
168 	}
169 	return 0;
170 }
171 
172 
173 /*
174  * called by SR*() commands provided by scuzz's scsireq
175  */
176 long
177 umsrequest(Umsc *umsc, ScsiPtr *cmd, ScsiPtr *data, int *status)
178 {
179 	Cbw cbw;
180 	Csw csw;
181 	int n;
182 	Ums *ums;
183 
184 	ums = umsc->ums;
185 
186 	memcpy(cbw.signature, "USBC", 4);
187 	cbw.tag = ++ums->seq;
188 	cbw.datalen = data->count;
189 	cbw.flags = data->write? CbwDataOut: CbwDataIn;
190 	cbw.lun = umsc->lun;
191 	if(cmd->count < 1 || cmd->count > 16)
192 		print("%s: umsrequest: bad cmd count: %ld\n", argv0, cmd->count);
193 
194 	cbw.len = cmd->count;
195 	assert(cmd->count <= sizeof(cbw.command));
196 	memcpy(cbw.command, cmd->p, cmd->count);
197 	memset(cbw.command + cmd->count, 0, sizeof(cbw.command) - cmd->count);
198 
199 	werrstr("");		/* we use %r later even for n == 0 */
200 
201 	if(diskdebug){
202 		fprint(2, "disk: cmd: tag %#lx: ", cbw.tag);
203 		for(n = 0; n < cbw.len; n++)
204 			fprint(2, " %2.2x", cbw.command[n]&0xFF);
205 		fprint(2, " datalen: %ld\n", cbw.datalen);
206 	}
207 	if(write(ums->epout->dfd, &cbw, CbwLen) != CbwLen){
208 		fprint(2, "disk: cmd: %r\n");
209 		goto Fail;
210 	}
211 	if(data->count != 0){
212 		if(data->write)
213 			n = write(ums->epout->dfd, data->p, data->count);
214 		else{
215 			memset(data->p, data->count, 0);
216 			n = read(ums->epin->dfd, data->p, data->count);
217 		}
218 		if(diskdebug)
219 			if(n < 0)
220 				fprint(2, "disk: data: %r\n");
221 			else
222 				fprint(2, "disk: data: %d bytes\n", n);
223 		if(n <= 0)
224 			if(data->write == 0)
225 				unstall(ums->dev, ums->epin, Ein);
226 	}
227 	n = read(ums->epin->dfd, &csw, CswLen);
228 	if(n <= 0){
229 		/* n == 0 means "stalled" */
230 		unstall(ums->dev, ums->epin, Ein);
231 		n = read(ums->epin->dfd, &csw, CswLen);
232 	}
233 	if(n != CswLen || strncmp(csw.signature, "USBS", 4) != 0){
234 		dprint(2, "disk: read n=%d: status: %r\n", n);
235 		goto Fail;
236 	}
237 	if(csw.tag != cbw.tag){
238 		dprint(2, "disk: status tag mismatch\n");
239 		goto Fail;
240 	}
241 	if(csw.status >= CswPhaseErr){
242 		dprint(2, "disk: phase error\n");
243 		goto Fail;
244 	}
245 	if(diskdebug){
246 		fprint(2, "status: %2.2ux residue: %ld\n",
247 			csw.status, csw.dataresidue);
248 		if(cbw.command[0] == ScmdRsense){
249 			fprint(2, "sense data:");
250 			for(n = 0; n < data->count - csw.dataresidue; n++)
251 				fprint(2, " %2.2x", data->p[n]);
252 			fprint(2, "\n");
253 		}
254 	}
255 	switch(csw.status){
256 	case CswOk:
257 		*status = STok;
258 		break;
259 	case CswFailed:
260 		*status = STcheck;
261 		break;
262 	default:
263 		dprint(2, "disk: phase error\n");
264 		goto Fail;
265 	}
266 	ums->nerrs = 0;
267 	return data->count - csw.dataresidue;
268 
269 Fail:
270 	*status = STharderr;
271 	if(ums->nerrs++ > 15){
272 		fprint(2, "disk: %s: too many errors: device detached\n", ums->dev->dir);
273 		umsfatal(ums);
274 	}else
275 		umsrecover(ums);
276 	return -1;
277 }
278 
279 static int
280 dwalk(Usbfs *fs, Fid *fid, char *name)
281 {
282 	int i;
283 	Qid qid;
284 
285 	qid = fid->qid;
286 	if((qid.type & QTDIR) == 0){
287 		werrstr("walk in non-directory");
288 		return -1;
289 	}
290 
291 	if(strcmp(name, "..") == 0)
292 		return 0;
293 
294 	for(i = 1; i < nelem(dirtab); i++)
295 		if(strcmp(name, dirtab[i].name) == 0){
296 			qid.path = i | fs->qid;
297 			qid.vers = 0;
298 			qid.type = dirtab[i].mode >> 24;
299 			fid->qid = qid;
300 			return 0;
301 		}
302 	werrstr(Enotfound);
303 	return -1;
304 }
305 
306 static void
307 dostat(Usbfs *fs, int path, Dir *d)
308 {
309 	Dirtab *t;
310 	Umsc *lun;
311 
312 	t = &dirtab[path];
313 	d->qid.path = path;
314 	d->qid.type = t->mode >> 24;
315 	d->mode = t->mode;
316 	d->name = t->name;
317 	lun = fs->aux;
318 	if(path == Qdata)
319 		d->length = lun->capacity;
320 	else
321 		d->length = 0;
322 }
323 
324 static int
325 dirgen(Usbfs *fs, Qid, int i, Dir *d, void*)
326 {
327 	i++;	/* skip dir */
328 	if(i >= Qmax)
329 		return -1;
330 	else{
331 		dostat(fs, i, d);
332 		d->qid.path |= fs->qid;
333 		return 0;
334 	}
335 }
336 
337 static int
338 dstat(Usbfs *fs, Qid qid, Dir *d)
339 {
340 	int path;
341 
342 	path = qid.path & ~fs->qid;
343 	dostat(fs, path, d);
344 	d->qid.path |= fs->qid;
345 	return 0;
346 }
347 
348 static int
349 dopen(Usbfs *fs, Fid *fid, int)
350 {
351 	ulong path;
352 	Umsc *lun;
353 
354 	path = fid->qid.path & ~fs->qid;
355 	lun = fs->aux;
356 	switch(path){
357 	case Qraw:
358 		lun->phase = Pcmd;
359 		break;
360 	}
361 	return 0;
362 }
363 
364 /*
365  * Upon SRread/SRwrite errors we assume the medium may have changed,
366  * and ask again for the capacity of the media.
367  * BUG: How to proceed to avoid confussing dossrv??
368  */
369 static long
370 dread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
371 {
372 	long bno, nb, len, off, n;
373 	ulong path;
374 	char buf[1024], *p;
375 	char *s;
376 	char *e;
377 	Umsc *lun;
378 	Ums *ums;
379 	Qid q;
380 
381 	q = fid->qid;
382 	path = fid->qid.path & ~fs->qid;
383 	ums = fs->dev->aux;
384 	lun = fs->aux;
385 	qlock(ums);
386 	switch(path){
387 	case Qdir:
388 		count = usbdirread(fs, q, data, count, offset, dirgen, nil);
389 		break;
390 	case Qctl:
391 		e = buf + sizeof(buf);
392 		s = seprint(buf, e, "%s lun %ld: ", fs->dev->dir, lun - &ums->lun[0]);
393 		if(lun->flags & Finqok)
394 			s = seprint(s, e, "inquiry %s ", lun->inq);
395 		if(lun->blocks > 0)
396 			s = seprint(s, e, "geometry %llud %ld", lun->blocks,
397 				lun->lbsize);
398 		s = seprint(s, e, "\n");
399 		count = usbreadbuf(data, count, offset, buf, s - buf);
400 		break;
401 	case Qraw:
402 		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
403 			qunlock(ums);
404 			return -1;
405 		}
406 		switch(lun->phase){
407 		case Pcmd:
408 			qunlock(ums);
409 			werrstr("phase error");
410 			return -1;
411 		case Pdata:
412 			lun->data.p = (uchar*)data;
413 			lun->data.count = count;
414 			lun->data.write = 0;
415 			count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
416 			lun->phase = Pstatus;
417 			if(count < 0){
418 				lun->lbsize = 0;	/* medium may have changed */
419 				qunlock(ums);
420 				return -1;
421 			}
422 			break;
423 		case Pstatus:
424 			n = snprint(buf, sizeof buf, "%11.0ud ", lun->status);
425 			count = usbreadbuf(data, count, 0LL, buf, n);
426 			lun->phase = Pcmd;
427 			break;
428 		}
429 		break;
430 	case Qdata:
431 		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
432 			qunlock(ums);
433 			return -1;
434 		}
435 		bno = offset / lun->lbsize;
436 		nb = (offset + count + lun->lbsize - 1) / lun->lbsize - bno;
437 		if(bno + nb > lun->blocks)
438 			nb = lun->blocks - bno;
439 		if(bno >= lun->blocks || nb == 0){
440 			count = 0;
441 			break;
442 		}
443 		if(nb * lun->lbsize > maxiosize)
444 			nb = maxiosize / lun->lbsize;
445 		p = emallocz(nb * lun->lbsize, 0);	/* could use a static buffer */
446 		lun->offset = offset / lun->lbsize;
447 		n = SRread(lun, p, nb * lun->lbsize);
448 		if(n < 0){
449 			free(p);
450 			lun->lbsize = 0;	/* medium may have changed */
451 			qunlock(ums);
452 			return -1;
453 		}
454 		len = count;
455 		off = offset % lun->lbsize;
456 		if(off + len > n)
457 			len = n - off;
458 		count = len;
459 		memmove(data, p + off, len);
460 		free(p);
461 		break;
462 	}
463 	qunlock(ums);
464 	return count;
465 }
466 
467 static long
468 dwrite(Usbfs *fs, Fid *fid, void *buf, long count, vlong offset)
469 {
470 	int bno, nb, len, off;
471 	ulong path;
472 	char *p;
473 	Ums *ums;
474 	Umsc *lun;
475 	char *data;
476 
477 	ums = fs->dev->aux;
478 	lun = fs->aux;
479 	path = fid->qid.path & ~fs->qid;
480 	data = buf;
481 	qlock(ums);
482 	switch(path){
483 	default:
484 		qunlock(ums);
485 		werrstr(Eperm);
486 		return -1;
487 	case Qraw:
488 		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
489 			qunlock(ums);
490 			return -1;
491 		}
492 		switch(lun->phase){
493 		case Pcmd:
494 			if(count != 6 && count != 10){
495 				qunlock(ums);
496 				werrstr("bad command length");
497 				return -1;
498 			}
499 			memmove(lun->rawcmd, data, count);
500 			lun->cmd.p = lun->rawcmd;
501 			lun->cmd.count = count;
502 			lun->cmd.write = 1;
503 			lun->phase = Pdata;
504 			break;
505 		case Pdata:
506 			lun->data.p = (uchar*)data;
507 			lun->data.count = count;
508 			lun->data.write = 1;
509 			count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
510 			lun->phase = Pstatus;
511 			if(count < 0){
512 				lun->lbsize = 0;	/* medium may have changed */
513 				qunlock(ums);
514 				return -1;
515 			}
516 			break;
517 		case Pstatus:
518 			lun->phase = Pcmd;
519 			qunlock(ums);
520 			werrstr("phase error");
521 			return -1;
522 		}
523 		break;
524 	case Qdata:
525 		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
526 			qunlock(ums);
527 			return -1;
528 		}
529 		bno = offset / lun->lbsize;
530 		nb = (offset + count + lun->lbsize-1) / lun->lbsize - bno;
531 		if(bno + nb > lun->blocks)
532 			nb = lun->blocks - bno;
533 		if(bno >= lun->blocks || nb == 0){
534 			count = 0;
535 			break;
536 		}
537 		if(nb * lun->lbsize > maxiosize)
538 			nb = maxiosize / lun->lbsize;
539 		p = emallocz(nb * lun->lbsize, 0);
540 		off = offset % lun->lbsize;
541 		len = count;
542 		if(off || (len % lun->lbsize) != 0){
543 			lun->offset = offset / lun->lbsize;
544 			count = SRread(lun, p, nb * lun->lbsize);
545 			if(count < 0){
546 				free(p);
547 				lun->lbsize = 0;	/* medium may have changed */
548 				qunlock(ums);
549 				return -1;
550 			}
551 			if(off + len > count)
552 				len = count - off;
553 		}
554 		memmove(p+off, data, len);
555 		lun->offset = offset / lun->lbsize;
556 		count = SRwrite(lun, p, nb * lun->lbsize);
557 		if(count < 0){
558 			free(p);
559 			lun->lbsize = 0;	/* medium may have changed */
560 			qunlock(ums);
561 			return -1;
562 		}
563 		if(off+len > count)
564 			len = count - off;
565 		count = len;
566 		free(p);
567 		break;
568 	}
569 	qunlock(ums);
570 	return count;
571 }
572 
573 int
574 findendpoints(Ums *ums)
575 {
576 	Ep *ep;
577 	Usbdev *ud;
578 	ulong csp;
579 	ulong sc;
580 	int i;
581 	int epin, epout;
582 
583 	epin = epout = -1;
584 	ud = ums->dev->usb;
585 	for(i = 0; i < nelem(ud->ep); i++){
586 		if((ep = ud->ep[i]) == nil)
587 			continue;
588 		csp = ep->iface->csp;
589 		sc = Subclass(csp);
590 		if(!(Class(csp) == Clstorage && (Proto(csp) == Protobulk)))
591 			continue;
592 		if(sc != Subatapi && sc != Sub8070 && sc != Subscsi)
593 			fprint(2, "disk: subclass %#ulx not supported. trying anyway\n", sc);
594 		if(ep->type == Ebulk){
595 			if(ep->dir == Eboth || ep->dir == Ein)
596 				if(epin == -1)
597 					epin =  ep->id;
598 			if(ep->dir == Eboth || ep->dir == Eout)
599 				if(epout == -1)
600 					epout = ep->id;
601 		}
602 	}
603 	dprint(2, "disk: ep ids: in %d out %d\n", epin, epout);
604 	if(epin == -1 || epout == -1)
605 		return -1;
606 	ums->epin = openep(ums->dev, epin);
607 	if(ums->epin == nil){
608 		fprint(2, "disk: openep %d: %r\n", epin);
609 		return -1;
610 	}
611 	if(epout == epin){
612 		incref(ums->epin);
613 		ums->epout = ums->epin;
614 	}else
615 		ums->epout = openep(ums->dev, epout);
616 	if(ums->epout == nil){
617 		fprint(2, "disk: openep %d: %r\n", epout);
618 		closedev(ums->epin);
619 		return -1;
620 	}
621 	if(ums->epin == ums->epout)
622 		opendevdata(ums->epin, ORDWR);
623 	else{
624 		opendevdata(ums->epin, OREAD);
625 		opendevdata(ums->epout, OWRITE);
626 	}
627 	if(ums->epin->dfd < 0 || ums->epout->dfd < 0){
628 		fprint(2, "disk: open i/o ep data: %r\n");
629 		closedev(ums->epin);
630 		closedev(ums->epout);
631 		return -1;
632 	}
633 	dprint(2, "disk: ep in %s out %s\n", ums->epin->dir, ums->epout->dir);
634 
635 	devctl(ums->epin, "timeout 2000");
636 	devctl(ums->epout, "timeout 2000");
637 
638 	if(usbdebug > 1 || diskdebug > 2){
639 		devctl(ums->epin, "debug 1");
640 		devctl(ums->epout, "debug 1");
641 		devctl(ums->dev, "debug 1");
642 	}
643 	return 0;
644 }
645 
646 static int
647 usage(void)
648 {
649 	werrstr("usage: usb/disk [-d]");
650 	return -1;
651 }
652 
653 static void
654 umsdevfree(void *a)
655 {
656 	Ums *ums = a;
657 
658 	if(ums == nil)
659 		return;
660 	closedev(ums->epin);
661 	closedev(ums->epout);
662 	ums->epin = ums->epout = nil;
663 	free(ums->lun);
664 	free(ums);
665 }
666 
667 static Usbfs diskfs = {
668 	.walk = dwalk,
669 	.open =	 dopen,
670 	.read =	 dread,
671 	.write = dwrite,
672 	.stat =	 dstat,
673 };
674 
675 int
676 diskmain(Dev *dev, int argc, char **argv)
677 {
678 	Ums *ums;
679 	Umsc *lun;
680 	int i;
681 
682 	ARGBEGIN{
683 	case 'd':
684 		scsidebug(diskdebug);
685 		diskdebug++;
686 		break;
687 	default:
688 		return usage();
689 	}ARGEND
690 	if(argc != 0)
691 		return usage();
692 
693 	ums = dev->aux = emallocz(sizeof(Ums), 1);
694 	ums->maxlun = -1;
695 	ums->dev = dev;
696 	dev->free = umsdevfree;
697 	if(findendpoints(ums) < 0){
698 		werrstr("disk: endpoints not found");
699 		return -1;
700 	}
701 	if(umsinit(ums) < 0){
702 		dprint(2, "disk: umsinit: %r\n");
703 		return -1;
704 	}
705 
706 	for(i = 0; i <= ums->maxlun; i++){
707 		lun = &ums->lun[i];
708 		lun->fs = diskfs;
709 		snprint(lun->fs.name, sizeof(lun->fs.name), "sdU%d.%d", dev->id, i);
710 		lun->fs.dev = dev;
711 		incref(dev);
712 		lun->fs.aux = lun;
713 		usbfsadd(&lun->fs);
714 	}
715 	closedev(dev);
716 	return 0;
717 }
718