usb_msctest.c revision 194228
1/* $FreeBSD: head/sys/dev/usb/usb_msctest.c 194228 2009-06-15 01:02:43Z thompsa $ */
2/*-
3 * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27/*
28 * The following file contains code that will detect USB autoinstall
29 * disks.
30 *
31 * TODO: Potentially we could add code to automatically detect USB
32 * mass storage quirks for not supported SCSI commands!
33 */
34
35#include <dev/usb/usb_mfunc.h>
36#include <dev/usb/usb_error.h>
37#include <dev/usb/usb.h>
38
39#define	USB_DEBUG_VAR usb_debug
40
41#include <dev/usb/usb_core.h>
42#include <dev/usb/usb_busdma.h>
43#include <dev/usb/usb_process.h>
44#include <dev/usb/usb_transfer.h>
45#include <dev/usb/usb_msctest.h>
46#include <dev/usb/usb_debug.h>
47#include <dev/usb/usb_busdma.h>
48#include <dev/usb/usb_device.h>
49#include <dev/usb/usb_request.h>
50#include <dev/usb/usb_util.h>
51#include <dev/usb/usb_lookup.h>
52
53#include <dev/usb/usb_mfunc.h>
54#include <dev/usb/usb_error.h>
55#include <dev/usb/usb.h>
56
57enum {
58	ST_COMMAND,
59	ST_DATA_RD,
60	ST_DATA_RD_CS,
61	ST_DATA_WR,
62	ST_DATA_WR_CS,
63	ST_STATUS,
64	ST_MAX,
65};
66
67enum {
68	DIR_IN,
69	DIR_OUT,
70	DIR_NONE,
71};
72
73#define	BULK_SIZE		64	/* dummy */
74
75/* Command Block Wrapper */
76struct bbb_cbw {
77	uDWord	dCBWSignature;
78#define	CBWSIGNATURE	0x43425355
79	uDWord	dCBWTag;
80	uDWord	dCBWDataTransferLength;
81	uByte	bCBWFlags;
82#define	CBWFLAGS_OUT	0x00
83#define	CBWFLAGS_IN	0x80
84	uByte	bCBWLUN;
85	uByte	bCDBLength;
86#define	CBWCDBLENGTH	16
87	uByte	CBWCDB[CBWCDBLENGTH];
88} __packed;
89
90/* Command Status Wrapper */
91struct bbb_csw {
92	uDWord	dCSWSignature;
93#define	CSWSIGNATURE	0x53425355
94	uDWord	dCSWTag;
95	uDWord	dCSWDataResidue;
96	uByte	bCSWStatus;
97#define	CSWSTATUS_GOOD	0x0
98#define	CSWSTATUS_FAILED	0x1
99#define	CSWSTATUS_PHASE	0x2
100} __packed;
101
102struct bbb_transfer {
103	struct mtx mtx;
104	struct cv cv;
105	struct bbb_cbw cbw;
106	struct bbb_csw csw;
107
108	struct usb_xfer *xfer[ST_MAX];
109
110	uint8_t *data_ptr;
111
112	usb_size_t data_len;		/* bytes */
113	usb_size_t data_rem;		/* bytes */
114	usb_timeout_t data_timeout;	/* ms */
115	usb_frlength_t actlen;		/* bytes */
116
117	uint8_t	cmd_len;		/* bytes */
118	uint8_t	dir;
119	uint8_t	lun;
120	uint8_t	state;
121	uint8_t	error;
122	uint8_t	status_try;
123
124	uint8_t	buffer[256];
125};
126
127static usb_callback_t bbb_command_callback;
128static usb_callback_t bbb_data_read_callback;
129static usb_callback_t bbb_data_rd_cs_callback;
130static usb_callback_t bbb_data_write_callback;
131static usb_callback_t bbb_data_wr_cs_callback;
132static usb_callback_t bbb_status_callback;
133
134static const struct usb_config bbb_config[ST_MAX] = {
135
136	[ST_COMMAND] = {
137		.type = UE_BULK,
138		.endpoint = UE_ADDR_ANY,
139		.direction = UE_DIR_OUT,
140		.bufsize = sizeof(struct bbb_cbw),
141		.callback = &bbb_command_callback,
142		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
143	},
144
145	[ST_DATA_RD] = {
146		.type = UE_BULK,
147		.endpoint = UE_ADDR_ANY,
148		.direction = UE_DIR_IN,
149		.bufsize = BULK_SIZE,
150		.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,},
151		.callback = &bbb_data_read_callback,
152		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
153	},
154
155	[ST_DATA_RD_CS] = {
156		.type = UE_CONTROL,
157		.endpoint = 0x00,	/* Control pipe */
158		.direction = UE_DIR_ANY,
159		.bufsize = sizeof(struct usb_device_request),
160		.callback = &bbb_data_rd_cs_callback,
161		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
162	},
163
164	[ST_DATA_WR] = {
165		.type = UE_BULK,
166		.endpoint = UE_ADDR_ANY,
167		.direction = UE_DIR_OUT,
168		.bufsize = BULK_SIZE,
169		.flags = {.proxy_buffer = 1,},
170		.callback = &bbb_data_write_callback,
171		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
172	},
173
174	[ST_DATA_WR_CS] = {
175		.type = UE_CONTROL,
176		.endpoint = 0x00,	/* Control pipe */
177		.direction = UE_DIR_ANY,
178		.bufsize = sizeof(struct usb_device_request),
179		.callback = &bbb_data_wr_cs_callback,
180		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
181	},
182
183	[ST_STATUS] = {
184		.type = UE_BULK,
185		.endpoint = UE_ADDR_ANY,
186		.direction = UE_DIR_IN,
187		.bufsize = sizeof(struct bbb_csw),
188		.flags = {.short_xfer_ok = 1,},
189		.callback = &bbb_status_callback,
190		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
191	},
192};
193
194static void
195bbb_done(struct bbb_transfer *sc, uint8_t error)
196{
197	struct usb_xfer *xfer;
198
199	xfer = sc->xfer[sc->state];
200
201	/* verify the error code */
202
203	if (error) {
204		switch (USB_GET_STATE(xfer)) {
205		case USB_ST_SETUP:
206		case USB_ST_TRANSFERRED:
207			error = 1;
208			break;
209		default:
210			error = 2;
211			break;
212		}
213	}
214	sc->error = error;
215	sc->state = ST_COMMAND;
216	sc->status_try = 1;
217	cv_signal(&sc->cv);
218}
219
220static void
221bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index)
222{
223	sc->state = xfer_index;
224	usbd_transfer_start(sc->xfer[xfer_index]);
225}
226
227static void
228bbb_data_clear_stall_callback(struct usb_xfer *xfer,
229    uint8_t next_xfer, uint8_t stall_xfer)
230{
231	struct bbb_transfer *sc = xfer->priv_sc;
232
233	if (usbd_clear_stall_callback(xfer, sc->xfer[stall_xfer])) {
234		switch (USB_GET_STATE(xfer)) {
235		case USB_ST_SETUP:
236		case USB_ST_TRANSFERRED:
237			bbb_transfer_start(sc, next_xfer);
238			break;
239		default:
240			bbb_done(sc, 1);
241			break;
242		}
243	}
244}
245
246static void
247bbb_command_callback(struct usb_xfer *xfer)
248{
249	struct bbb_transfer *sc = xfer->priv_sc;
250	uint32_t tag;
251
252	switch (USB_GET_STATE(xfer)) {
253	case USB_ST_TRANSFERRED:
254		bbb_transfer_start
255		    (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD :
256		    (sc->dir == DIR_OUT) ? ST_DATA_WR :
257		    ST_STATUS));
258		break;
259
260	case USB_ST_SETUP:
261		sc->status_try = 0;
262		tag = UGETDW(sc->cbw.dCBWTag) + 1;
263		USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE);
264		USETDW(sc->cbw.dCBWTag, tag);
265		USETDW(sc->cbw.dCBWDataTransferLength, (uint32_t)sc->data_len);
266		sc->cbw.bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT);
267		sc->cbw.bCBWLUN = sc->lun;
268		sc->cbw.bCDBLength = sc->cmd_len;
269		if (sc->cbw.bCDBLength > sizeof(sc->cbw.CBWCDB)) {
270			sc->cbw.bCDBLength = sizeof(sc->cbw.CBWCDB);
271			DPRINTFN(0, "Truncating long command!\n");
272		}
273		xfer->frlengths[0] = sizeof(sc->cbw);
274
275		usbd_set_frame_data(xfer, &sc->cbw, 0);
276		usbd_transfer_submit(xfer);
277		break;
278
279	default:			/* Error */
280		bbb_done(sc, 1);
281		break;
282	}
283}
284
285static void
286bbb_data_read_callback(struct usb_xfer *xfer)
287{
288	struct bbb_transfer *sc = xfer->priv_sc;
289	usb_frlength_t max_bulk = xfer->max_data_length;
290
291	switch (USB_GET_STATE(xfer)) {
292	case USB_ST_TRANSFERRED:
293		sc->data_rem -= xfer->actlen;
294		sc->data_ptr += xfer->actlen;
295		sc->actlen += xfer->actlen;
296
297		if (xfer->actlen < xfer->sumlen) {
298			/* short transfer */
299			sc->data_rem = 0;
300		}
301	case USB_ST_SETUP:
302		DPRINTF("max_bulk=%d, data_rem=%d\n",
303		    max_bulk, sc->data_rem);
304
305		if (sc->data_rem == 0) {
306			bbb_transfer_start(sc, ST_STATUS);
307			break;
308		}
309		if (max_bulk > sc->data_rem) {
310			max_bulk = sc->data_rem;
311		}
312		xfer->timeout = sc->data_timeout;
313		xfer->frlengths[0] = max_bulk;
314
315		usbd_set_frame_data(xfer, sc->data_ptr, 0);
316		usbd_transfer_submit(xfer);
317		break;
318
319	default:			/* Error */
320		if (xfer->error == USB_ERR_CANCELLED) {
321			bbb_done(sc, 1);
322		} else {
323			bbb_transfer_start(sc, ST_DATA_RD_CS);
324		}
325		break;
326	}
327}
328
329static void
330bbb_data_rd_cs_callback(struct usb_xfer *xfer)
331{
332	bbb_data_clear_stall_callback(xfer, ST_STATUS,
333	    ST_DATA_RD);
334}
335
336static void
337bbb_data_write_callback(struct usb_xfer *xfer)
338{
339	struct bbb_transfer *sc = xfer->priv_sc;
340	usb_frlength_t max_bulk = xfer->max_data_length;
341
342	switch (USB_GET_STATE(xfer)) {
343	case USB_ST_TRANSFERRED:
344		sc->data_rem -= xfer->actlen;
345		sc->data_ptr += xfer->actlen;
346		sc->actlen += xfer->actlen;
347
348		if (xfer->actlen < xfer->sumlen) {
349			/* short transfer */
350			sc->data_rem = 0;
351		}
352	case USB_ST_SETUP:
353		DPRINTF("max_bulk=%d, data_rem=%d\n",
354		    max_bulk, sc->data_rem);
355
356		if (sc->data_rem == 0) {
357			bbb_transfer_start(sc, ST_STATUS);
358			return;
359		}
360		if (max_bulk > sc->data_rem) {
361			max_bulk = sc->data_rem;
362		}
363		xfer->timeout = sc->data_timeout;
364		xfer->frlengths[0] = max_bulk;
365
366		usbd_set_frame_data(xfer, sc->data_ptr, 0);
367		usbd_transfer_submit(xfer);
368		return;
369
370	default:			/* Error */
371		if (xfer->error == USB_ERR_CANCELLED) {
372			bbb_done(sc, 1);
373		} else {
374			bbb_transfer_start(sc, ST_DATA_WR_CS);
375		}
376		return;
377
378	}
379}
380
381static void
382bbb_data_wr_cs_callback(struct usb_xfer *xfer)
383{
384	bbb_data_clear_stall_callback(xfer, ST_STATUS,
385	    ST_DATA_WR);
386}
387
388static void
389bbb_status_callback(struct usb_xfer *xfer)
390{
391	struct bbb_transfer *sc = xfer->priv_sc;
392
393	switch (USB_GET_STATE(xfer)) {
394	case USB_ST_TRANSFERRED:
395
396		/* very simple status check */
397
398		if (xfer->actlen < sizeof(sc->csw)) {
399			bbb_done(sc, 1);/* error */
400		} else if (sc->csw.bCSWStatus == CSWSTATUS_GOOD) {
401			bbb_done(sc, 0);/* success */
402		} else {
403			bbb_done(sc, 1);/* error */
404		}
405		break;
406
407	case USB_ST_SETUP:
408		xfer->frlengths[0] = sizeof(sc->csw);
409
410		usbd_set_frame_data(xfer, &sc->csw, 0);
411		usbd_transfer_submit(xfer);
412		break;
413
414	default:
415		DPRINTFN(0, "Failed to read CSW: %s, try %d\n",
416		    usbd_errstr(xfer->error), sc->status_try);
417
418		if ((xfer->error == USB_ERR_CANCELLED) ||
419		    (sc->status_try)) {
420			bbb_done(sc, 1);
421		} else {
422			sc->status_try = 1;
423			bbb_transfer_start(sc, ST_DATA_RD_CS);
424		}
425		break;
426	}
427}
428
429/*------------------------------------------------------------------------*
430 *	bbb_command_start - execute a SCSI command synchronously
431 *
432 * Return values
433 * 0: Success
434 * Else: Failure
435 *------------------------------------------------------------------------*/
436static uint8_t
437bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun,
438    void *data_ptr, usb_size_t data_len, uint8_t cmd_len,
439    usb_timeout_t data_timeout)
440{
441	sc->lun = lun;
442	sc->dir = data_len ? dir : DIR_NONE;
443	sc->data_ptr = data_ptr;
444	sc->data_len = data_len;
445	sc->data_rem = data_len;
446	sc->data_timeout = (data_timeout + USB_MS_HZ);
447	sc->actlen = 0;
448	sc->cmd_len = cmd_len;
449
450	usbd_transfer_start(sc->xfer[sc->state]);
451
452	while (usbd_transfer_pending(sc->xfer[sc->state])) {
453		cv_wait(&sc->cv, &sc->mtx);
454	}
455	return (sc->error);
456}
457
458/*------------------------------------------------------------------------*
459 *	usb_test_autoinstall
460 *
461 * Return values:
462 * 0: This interface is an auto install disk (CD-ROM)
463 * Else: Not an auto install disk.
464 *------------------------------------------------------------------------*/
465usb_error_t
466usb_test_autoinstall(struct usb_device *udev, uint8_t iface_index,
467    uint8_t do_eject)
468{
469	struct usb_interface *iface;
470	struct usb_interface_descriptor *id;
471	usb_error_t err;
472	uint8_t timeout;
473	uint8_t sid_type;
474	struct bbb_transfer *sc;
475
476	if (udev == NULL) {
477		return (USB_ERR_INVAL);
478	}
479	iface = usbd_get_iface(udev, iface_index);
480	if (iface == NULL) {
481		return (USB_ERR_INVAL);
482	}
483	id = iface->idesc;
484	if (id == NULL) {
485		return (USB_ERR_INVAL);
486	}
487	if (id->bInterfaceClass != UICLASS_MASS) {
488		return (USB_ERR_INVAL);
489	}
490	switch (id->bInterfaceSubClass) {
491	case UISUBCLASS_SCSI:
492	case UISUBCLASS_UFI:
493		break;
494	default:
495		return (USB_ERR_INVAL);
496	}
497
498	switch (id->bInterfaceProtocol) {
499	case UIPROTO_MASS_BBB_OLD:
500	case UIPROTO_MASS_BBB:
501		break;
502	default:
503		return (USB_ERR_INVAL);
504	}
505
506	sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO);
507	if (sc == NULL) {
508		return (USB_ERR_NOMEM);
509	}
510	mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF);
511	cv_init(&sc->cv, "WBBB");
512
513	err = usbd_transfer_setup(udev,
514	    &iface_index, sc->xfer, bbb_config,
515	    ST_MAX, sc, &sc->mtx);
516
517	if (err) {
518		goto done;
519	}
520	mtx_lock(&sc->mtx);
521
522	timeout = 4;			/* tries */
523
524repeat_inquiry:
525
526	sc->cbw.CBWCDB[0] = 0x12;	/* INQUIRY */
527	sc->cbw.CBWCDB[1] = 0;
528	sc->cbw.CBWCDB[2] = 0;
529	sc->cbw.CBWCDB[3] = 0;
530	sc->cbw.CBWCDB[4] = 0x24;	/* length */
531	sc->cbw.CBWCDB[5] = 0;
532	err = bbb_command_start(sc, DIR_IN, 0,
533	    sc->buffer, 0x24, 6, USB_MS_HZ);
534
535	if ((sc->actlen != 0) && (err == 0)) {
536		sid_type = sc->buffer[0] & 0x1F;
537		if (sid_type == 0x05) {
538			/* CD-ROM */
539			if (do_eject) {
540				/* 0: opcode: SCSI START/STOP */
541				sc->cbw.CBWCDB[0] = 0x1b;
542				/* 1: byte2: Not immediate */
543				sc->cbw.CBWCDB[1] = 0x00;
544				/* 2..3: reserved */
545				sc->cbw.CBWCDB[2] = 0x00;
546				sc->cbw.CBWCDB[3] = 0x00;
547				/* 4: Load/Eject command */
548				sc->cbw.CBWCDB[4] = 0x02;
549				/* 5: control */
550				sc->cbw.CBWCDB[5] = 0x00;
551				err = bbb_command_start(sc, DIR_OUT, 0,
552				    NULL, 0, 6, USB_MS_HZ);
553
554				DPRINTFN(0, "Eject CD command "
555				    "status: %s\n", usbd_errstr(err));
556			}
557			err = 0;
558			goto done;
559		}
560	} else if ((err != 2) && --timeout) {
561		usb_pause_mtx(&sc->mtx, hz);
562		goto repeat_inquiry;
563	}
564	err = USB_ERR_INVAL;
565	goto done;
566
567done:
568	mtx_unlock(&sc->mtx);
569	usbd_transfer_unsetup(sc->xfer, ST_MAX);
570	mtx_destroy(&sc->mtx);
571	cv_destroy(&sc->cv);
572	free(sc, M_USB);
573	return (err);
574}
575