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