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