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