xref: /freebsd/sys/dev/usb/usb_msctest.c (revision 02ac6454880b59bbc5f3f74dffaffa90b30adc8b)
102ac6454SAndrew Thompson /* $FreeBSD$ */
202ac6454SAndrew Thompson /*-
302ac6454SAndrew Thompson  * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
402ac6454SAndrew Thompson  *
502ac6454SAndrew Thompson  * Redistribution and use in source and binary forms, with or without
602ac6454SAndrew Thompson  * modification, are permitted provided that the following conditions
702ac6454SAndrew Thompson  * are met:
802ac6454SAndrew Thompson  * 1. Redistributions of source code must retain the above copyright
902ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer.
1002ac6454SAndrew Thompson  * 2. Redistributions in binary form must reproduce the above copyright
1102ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer in the
1202ac6454SAndrew Thompson  *    documentation and/or other materials provided with the distribution.
1302ac6454SAndrew Thompson  *
1402ac6454SAndrew Thompson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1502ac6454SAndrew Thompson  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1602ac6454SAndrew Thompson  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1702ac6454SAndrew Thompson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1802ac6454SAndrew Thompson  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1902ac6454SAndrew Thompson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2002ac6454SAndrew Thompson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2102ac6454SAndrew Thompson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2202ac6454SAndrew Thompson  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2302ac6454SAndrew Thompson  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2402ac6454SAndrew Thompson  * SUCH DAMAGE.
2502ac6454SAndrew Thompson  */
2602ac6454SAndrew Thompson 
2702ac6454SAndrew Thompson /*
2802ac6454SAndrew Thompson  * The following file contains code that will detect USB autoinstall
2902ac6454SAndrew Thompson  * disks.
3002ac6454SAndrew Thompson  *
3102ac6454SAndrew Thompson  * TODO: Potentially we could add code to automatically detect USB
3202ac6454SAndrew Thompson  * mass storage quirks for not supported SCSI commands!
3302ac6454SAndrew Thompson  */
3402ac6454SAndrew Thompson 
3502ac6454SAndrew Thompson #include <dev/usb/usb_defs.h>
3602ac6454SAndrew Thompson #include <dev/usb/usb_mfunc.h>
3702ac6454SAndrew Thompson #include <dev/usb/usb_error.h>
3802ac6454SAndrew Thompson #include <dev/usb/usb.h>
3902ac6454SAndrew Thompson 
4002ac6454SAndrew Thompson #define	USB_DEBUG_VAR usb2_debug
4102ac6454SAndrew Thompson 
4202ac6454SAndrew Thompson #include <dev/usb/usb_core.h>
4302ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
4402ac6454SAndrew Thompson #include <dev/usb/usb_process.h>
4502ac6454SAndrew Thompson #include <dev/usb/usb_transfer.h>
4602ac6454SAndrew Thompson #include <dev/usb/usb_msctest.h>
4702ac6454SAndrew Thompson #include <dev/usb/usb_debug.h>
4802ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
4902ac6454SAndrew Thompson #include <dev/usb/usb_device.h>
5002ac6454SAndrew Thompson #include <dev/usb/usb_request.h>
5102ac6454SAndrew Thompson #include <dev/usb/usb_util.h>
5202ac6454SAndrew Thompson #include <dev/usb/usb_lookup.h>
5302ac6454SAndrew Thompson 
5402ac6454SAndrew Thompson #include <dev/usb/usb_mfunc.h>
5502ac6454SAndrew Thompson #include <dev/usb/usb_error.h>
5602ac6454SAndrew Thompson #include <dev/usb/usb.h>
5702ac6454SAndrew Thompson 
5802ac6454SAndrew Thompson enum {
5902ac6454SAndrew Thompson 	ST_COMMAND,
6002ac6454SAndrew Thompson 	ST_DATA_RD,
6102ac6454SAndrew Thompson 	ST_DATA_RD_CS,
6202ac6454SAndrew Thompson 	ST_DATA_WR,
6302ac6454SAndrew Thompson 	ST_DATA_WR_CS,
6402ac6454SAndrew Thompson 	ST_STATUS,
6502ac6454SAndrew Thompson 	ST_MAX,
6602ac6454SAndrew Thompson };
6702ac6454SAndrew Thompson 
6802ac6454SAndrew Thompson enum {
6902ac6454SAndrew Thompson 	DIR_IN,
7002ac6454SAndrew Thompson 	DIR_OUT,
7102ac6454SAndrew Thompson 	DIR_NONE,
7202ac6454SAndrew Thompson };
7302ac6454SAndrew Thompson 
7402ac6454SAndrew Thompson #define	BULK_SIZE		64	/* dummy */
7502ac6454SAndrew Thompson 
7602ac6454SAndrew Thompson /* Command Block Wrapper */
7702ac6454SAndrew Thompson struct bbb_cbw {
7802ac6454SAndrew Thompson 	uDWord	dCBWSignature;
7902ac6454SAndrew Thompson #define	CBWSIGNATURE	0x43425355
8002ac6454SAndrew Thompson 	uDWord	dCBWTag;
8102ac6454SAndrew Thompson 	uDWord	dCBWDataTransferLength;
8202ac6454SAndrew Thompson 	uByte	bCBWFlags;
8302ac6454SAndrew Thompson #define	CBWFLAGS_OUT	0x00
8402ac6454SAndrew Thompson #define	CBWFLAGS_IN	0x80
8502ac6454SAndrew Thompson 	uByte	bCBWLUN;
8602ac6454SAndrew Thompson 	uByte	bCDBLength;
8702ac6454SAndrew Thompson #define	CBWCDBLENGTH	16
8802ac6454SAndrew Thompson 	uByte	CBWCDB[CBWCDBLENGTH];
8902ac6454SAndrew Thompson } __packed;
9002ac6454SAndrew Thompson 
9102ac6454SAndrew Thompson /* Command Status Wrapper */
9202ac6454SAndrew Thompson struct bbb_csw {
9302ac6454SAndrew Thompson 	uDWord	dCSWSignature;
9402ac6454SAndrew Thompson #define	CSWSIGNATURE	0x53425355
9502ac6454SAndrew Thompson 	uDWord	dCSWTag;
9602ac6454SAndrew Thompson 	uDWord	dCSWDataResidue;
9702ac6454SAndrew Thompson 	uByte	bCSWStatus;
9802ac6454SAndrew Thompson #define	CSWSTATUS_GOOD	0x0
9902ac6454SAndrew Thompson #define	CSWSTATUS_FAILED	0x1
10002ac6454SAndrew Thompson #define	CSWSTATUS_PHASE	0x2
10102ac6454SAndrew Thompson } __packed;
10202ac6454SAndrew Thompson 
10302ac6454SAndrew Thompson struct bbb_transfer {
10402ac6454SAndrew Thompson 	struct mtx mtx;
10502ac6454SAndrew Thompson 	struct cv cv;
10602ac6454SAndrew Thompson 	struct bbb_cbw cbw;
10702ac6454SAndrew Thompson 	struct bbb_csw csw;
10802ac6454SAndrew Thompson 
10902ac6454SAndrew Thompson 	struct usb2_xfer *xfer[ST_MAX];
11002ac6454SAndrew Thompson 
11102ac6454SAndrew Thompson 	uint8_t *data_ptr;
11202ac6454SAndrew Thompson 
11302ac6454SAndrew Thompson 	uint32_t data_len;		/* bytes */
11402ac6454SAndrew Thompson 	uint32_t data_rem;		/* bytes */
11502ac6454SAndrew Thompson 	uint32_t data_timeout;		/* ms */
11602ac6454SAndrew Thompson 	uint32_t actlen;		/* bytes */
11702ac6454SAndrew Thompson 
11802ac6454SAndrew Thompson 	uint8_t	cmd_len;		/* bytes */
11902ac6454SAndrew Thompson 	uint8_t	dir;
12002ac6454SAndrew Thompson 	uint8_t	lun;
12102ac6454SAndrew Thompson 	uint8_t	state;
12202ac6454SAndrew Thompson 	uint8_t	error;
12302ac6454SAndrew Thompson 	uint8_t	status_try;
12402ac6454SAndrew Thompson 
12502ac6454SAndrew Thompson 	uint8_t	buffer[256];
12602ac6454SAndrew Thompson };
12702ac6454SAndrew Thompson 
12802ac6454SAndrew Thompson static usb2_callback_t bbb_command_callback;
12902ac6454SAndrew Thompson static usb2_callback_t bbb_data_read_callback;
13002ac6454SAndrew Thompson static usb2_callback_t bbb_data_rd_cs_callback;
13102ac6454SAndrew Thompson static usb2_callback_t bbb_data_write_callback;
13202ac6454SAndrew Thompson static usb2_callback_t bbb_data_wr_cs_callback;
13302ac6454SAndrew Thompson static usb2_callback_t bbb_status_callback;
13402ac6454SAndrew Thompson 
13502ac6454SAndrew Thompson static const struct usb2_config bbb_config[ST_MAX] = {
13602ac6454SAndrew Thompson 
13702ac6454SAndrew Thompson 	[ST_COMMAND] = {
13802ac6454SAndrew Thompson 		.type = UE_BULK,
13902ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
14002ac6454SAndrew Thompson 		.direction = UE_DIR_OUT,
14102ac6454SAndrew Thompson 		.mh.bufsize = sizeof(struct bbb_cbw),
14202ac6454SAndrew Thompson 		.mh.flags = {},
14302ac6454SAndrew Thompson 		.mh.callback = &bbb_command_callback,
14402ac6454SAndrew Thompson 		.mh.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
14502ac6454SAndrew Thompson 	},
14602ac6454SAndrew Thompson 
14702ac6454SAndrew Thompson 	[ST_DATA_RD] = {
14802ac6454SAndrew Thompson 		.type = UE_BULK,
14902ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
15002ac6454SAndrew Thompson 		.direction = UE_DIR_IN,
15102ac6454SAndrew Thompson 		.mh.bufsize = BULK_SIZE,
15202ac6454SAndrew Thompson 		.mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,},
15302ac6454SAndrew Thompson 		.mh.callback = &bbb_data_read_callback,
15402ac6454SAndrew Thompson 		.mh.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
15502ac6454SAndrew Thompson 	},
15602ac6454SAndrew Thompson 
15702ac6454SAndrew Thompson 	[ST_DATA_RD_CS] = {
15802ac6454SAndrew Thompson 		.type = UE_CONTROL,
15902ac6454SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
16002ac6454SAndrew Thompson 		.direction = UE_DIR_ANY,
16102ac6454SAndrew Thompson 		.mh.bufsize = sizeof(struct usb2_device_request),
16202ac6454SAndrew Thompson 		.mh.flags = {},
16302ac6454SAndrew Thompson 		.mh.callback = &bbb_data_rd_cs_callback,
16402ac6454SAndrew Thompson 		.mh.timeout = 1 * USB_MS_HZ,	/* 1 second  */
16502ac6454SAndrew Thompson 	},
16602ac6454SAndrew Thompson 
16702ac6454SAndrew Thompson 	[ST_DATA_WR] = {
16802ac6454SAndrew Thompson 		.type = UE_BULK,
16902ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
17002ac6454SAndrew Thompson 		.direction = UE_DIR_OUT,
17102ac6454SAndrew Thompson 		.mh.bufsize = BULK_SIZE,
17202ac6454SAndrew Thompson 		.mh.flags = {.proxy_buffer = 1,},
17302ac6454SAndrew Thompson 		.mh.callback = &bbb_data_write_callback,
17402ac6454SAndrew Thompson 		.mh.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
17502ac6454SAndrew Thompson 	},
17602ac6454SAndrew Thompson 
17702ac6454SAndrew Thompson 	[ST_DATA_WR_CS] = {
17802ac6454SAndrew Thompson 		.type = UE_CONTROL,
17902ac6454SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
18002ac6454SAndrew Thompson 		.direction = UE_DIR_ANY,
18102ac6454SAndrew Thompson 		.mh.bufsize = sizeof(struct usb2_device_request),
18202ac6454SAndrew Thompson 		.mh.flags = {},
18302ac6454SAndrew Thompson 		.mh.callback = &bbb_data_wr_cs_callback,
18402ac6454SAndrew Thompson 		.mh.timeout = 1 * USB_MS_HZ,	/* 1 second  */
18502ac6454SAndrew Thompson 	},
18602ac6454SAndrew Thompson 
18702ac6454SAndrew Thompson 	[ST_STATUS] = {
18802ac6454SAndrew Thompson 		.type = UE_BULK,
18902ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
19002ac6454SAndrew Thompson 		.direction = UE_DIR_IN,
19102ac6454SAndrew Thompson 		.mh.bufsize = sizeof(struct bbb_csw),
19202ac6454SAndrew Thompson 		.mh.flags = {.short_xfer_ok = 1,},
19302ac6454SAndrew Thompson 		.mh.callback = &bbb_status_callback,
19402ac6454SAndrew Thompson 		.mh.timeout = 1 * USB_MS_HZ,	/* 1 second  */
19502ac6454SAndrew Thompson 	},
19602ac6454SAndrew Thompson };
19702ac6454SAndrew Thompson 
19802ac6454SAndrew Thompson static void
19902ac6454SAndrew Thompson bbb_done(struct bbb_transfer *sc, uint8_t error)
20002ac6454SAndrew Thompson {
20102ac6454SAndrew Thompson 	struct usb2_xfer *xfer;
20202ac6454SAndrew Thompson 
20302ac6454SAndrew Thompson 	xfer = sc->xfer[sc->state];
20402ac6454SAndrew Thompson 
20502ac6454SAndrew Thompson 	/* verify the error code */
20602ac6454SAndrew Thompson 
20702ac6454SAndrew Thompson 	if (error) {
20802ac6454SAndrew Thompson 		switch (USB_GET_STATE(xfer)) {
20902ac6454SAndrew Thompson 		case USB_ST_SETUP:
21002ac6454SAndrew Thompson 		case USB_ST_TRANSFERRED:
21102ac6454SAndrew Thompson 			error = 1;
21202ac6454SAndrew Thompson 			break;
21302ac6454SAndrew Thompson 		default:
21402ac6454SAndrew Thompson 			error = 2;
21502ac6454SAndrew Thompson 			break;
21602ac6454SAndrew Thompson 		}
21702ac6454SAndrew Thompson 	}
21802ac6454SAndrew Thompson 	sc->error = error;
21902ac6454SAndrew Thompson 	sc->state = ST_COMMAND;
22002ac6454SAndrew Thompson 	sc->status_try = 1;
22102ac6454SAndrew Thompson 	usb2_cv_signal(&sc->cv);
22202ac6454SAndrew Thompson }
22302ac6454SAndrew Thompson 
22402ac6454SAndrew Thompson static void
22502ac6454SAndrew Thompson bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index)
22602ac6454SAndrew Thompson {
22702ac6454SAndrew Thompson 	sc->state = xfer_index;
22802ac6454SAndrew Thompson 	usb2_transfer_start(sc->xfer[xfer_index]);
22902ac6454SAndrew Thompson }
23002ac6454SAndrew Thompson 
23102ac6454SAndrew Thompson static void
23202ac6454SAndrew Thompson bbb_data_clear_stall_callback(struct usb2_xfer *xfer,
23302ac6454SAndrew Thompson     uint8_t next_xfer, uint8_t stall_xfer)
23402ac6454SAndrew Thompson {
23502ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
23602ac6454SAndrew Thompson 
23702ac6454SAndrew Thompson 	if (usb2_clear_stall_callback(xfer, sc->xfer[stall_xfer])) {
23802ac6454SAndrew Thompson 		switch (USB_GET_STATE(xfer)) {
23902ac6454SAndrew Thompson 		case USB_ST_SETUP:
24002ac6454SAndrew Thompson 		case USB_ST_TRANSFERRED:
24102ac6454SAndrew Thompson 			bbb_transfer_start(sc, next_xfer);
24202ac6454SAndrew Thompson 			break;
24302ac6454SAndrew Thompson 		default:
24402ac6454SAndrew Thompson 			bbb_done(sc, 1);
24502ac6454SAndrew Thompson 			break;
24602ac6454SAndrew Thompson 		}
24702ac6454SAndrew Thompson 	}
24802ac6454SAndrew Thompson }
24902ac6454SAndrew Thompson 
25002ac6454SAndrew Thompson static void
25102ac6454SAndrew Thompson bbb_command_callback(struct usb2_xfer *xfer)
25202ac6454SAndrew Thompson {
25302ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
25402ac6454SAndrew Thompson 	uint32_t tag;
25502ac6454SAndrew Thompson 
25602ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
25702ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
25802ac6454SAndrew Thompson 		bbb_transfer_start
25902ac6454SAndrew Thompson 		    (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD :
26002ac6454SAndrew Thompson 		    (sc->dir == DIR_OUT) ? ST_DATA_WR :
26102ac6454SAndrew Thompson 		    ST_STATUS));
26202ac6454SAndrew Thompson 		break;
26302ac6454SAndrew Thompson 
26402ac6454SAndrew Thompson 	case USB_ST_SETUP:
26502ac6454SAndrew Thompson 		sc->status_try = 0;
26602ac6454SAndrew Thompson 		tag = UGETDW(sc->cbw.dCBWTag) + 1;
26702ac6454SAndrew Thompson 		USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE);
26802ac6454SAndrew Thompson 		USETDW(sc->cbw.dCBWTag, tag);
26902ac6454SAndrew Thompson 		USETDW(sc->cbw.dCBWDataTransferLength, sc->data_len);
27002ac6454SAndrew Thompson 		sc->cbw.bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT);
27102ac6454SAndrew Thompson 		sc->cbw.bCBWLUN = sc->lun;
27202ac6454SAndrew Thompson 		sc->cbw.bCDBLength = sc->cmd_len;
27302ac6454SAndrew Thompson 		if (sc->cbw.bCDBLength > sizeof(sc->cbw.CBWCDB)) {
27402ac6454SAndrew Thompson 			sc->cbw.bCDBLength = sizeof(sc->cbw.CBWCDB);
27502ac6454SAndrew Thompson 			DPRINTFN(0, "Truncating long command!\n");
27602ac6454SAndrew Thompson 		}
27702ac6454SAndrew Thompson 		xfer->frlengths[0] = sizeof(sc->cbw);
27802ac6454SAndrew Thompson 
27902ac6454SAndrew Thompson 		usb2_set_frame_data(xfer, &sc->cbw, 0);
28002ac6454SAndrew Thompson 		usb2_start_hardware(xfer);
28102ac6454SAndrew Thompson 		break;
28202ac6454SAndrew Thompson 
28302ac6454SAndrew Thompson 	default:			/* Error */
28402ac6454SAndrew Thompson 		bbb_done(sc, 1);
28502ac6454SAndrew Thompson 		break;
28602ac6454SAndrew Thompson 	}
28702ac6454SAndrew Thompson }
28802ac6454SAndrew Thompson 
28902ac6454SAndrew Thompson static void
29002ac6454SAndrew Thompson bbb_data_read_callback(struct usb2_xfer *xfer)
29102ac6454SAndrew Thompson {
29202ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
29302ac6454SAndrew Thompson 	uint32_t max_bulk = xfer->max_data_length;
29402ac6454SAndrew Thompson 
29502ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
29602ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
29702ac6454SAndrew Thompson 		sc->data_rem -= xfer->actlen;
29802ac6454SAndrew Thompson 		sc->data_ptr += xfer->actlen;
29902ac6454SAndrew Thompson 		sc->actlen += xfer->actlen;
30002ac6454SAndrew Thompson 
30102ac6454SAndrew Thompson 		if (xfer->actlen < xfer->sumlen) {
30202ac6454SAndrew Thompson 			/* short transfer */
30302ac6454SAndrew Thompson 			sc->data_rem = 0;
30402ac6454SAndrew Thompson 		}
30502ac6454SAndrew Thompson 	case USB_ST_SETUP:
30602ac6454SAndrew Thompson 		DPRINTF("max_bulk=%d, data_rem=%d\n",
30702ac6454SAndrew Thompson 		    max_bulk, sc->data_rem);
30802ac6454SAndrew Thompson 
30902ac6454SAndrew Thompson 		if (sc->data_rem == 0) {
31002ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_STATUS);
31102ac6454SAndrew Thompson 			break;
31202ac6454SAndrew Thompson 		}
31302ac6454SAndrew Thompson 		if (max_bulk > sc->data_rem) {
31402ac6454SAndrew Thompson 			max_bulk = sc->data_rem;
31502ac6454SAndrew Thompson 		}
31602ac6454SAndrew Thompson 		xfer->timeout = sc->data_timeout;
31702ac6454SAndrew Thompson 		xfer->frlengths[0] = max_bulk;
31802ac6454SAndrew Thompson 
31902ac6454SAndrew Thompson 		usb2_set_frame_data(xfer, sc->data_ptr, 0);
32002ac6454SAndrew Thompson 		usb2_start_hardware(xfer);
32102ac6454SAndrew Thompson 		break;
32202ac6454SAndrew Thompson 
32302ac6454SAndrew Thompson 	default:			/* Error */
32402ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
32502ac6454SAndrew Thompson 			bbb_done(sc, 1);
32602ac6454SAndrew Thompson 		} else {
32702ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_RD_CS);
32802ac6454SAndrew Thompson 		}
32902ac6454SAndrew Thompson 		break;
33002ac6454SAndrew Thompson 	}
33102ac6454SAndrew Thompson }
33202ac6454SAndrew Thompson 
33302ac6454SAndrew Thompson static void
33402ac6454SAndrew Thompson bbb_data_rd_cs_callback(struct usb2_xfer *xfer)
33502ac6454SAndrew Thompson {
33602ac6454SAndrew Thompson 	bbb_data_clear_stall_callback(xfer, ST_STATUS,
33702ac6454SAndrew Thompson 	    ST_DATA_RD);
33802ac6454SAndrew Thompson }
33902ac6454SAndrew Thompson 
34002ac6454SAndrew Thompson static void
34102ac6454SAndrew Thompson bbb_data_write_callback(struct usb2_xfer *xfer)
34202ac6454SAndrew Thompson {
34302ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
34402ac6454SAndrew Thompson 	uint32_t max_bulk = xfer->max_data_length;
34502ac6454SAndrew Thompson 
34602ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
34702ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
34802ac6454SAndrew Thompson 		sc->data_rem -= xfer->actlen;
34902ac6454SAndrew Thompson 		sc->data_ptr += xfer->actlen;
35002ac6454SAndrew Thompson 		sc->actlen += xfer->actlen;
35102ac6454SAndrew Thompson 
35202ac6454SAndrew Thompson 		if (xfer->actlen < xfer->sumlen) {
35302ac6454SAndrew Thompson 			/* short transfer */
35402ac6454SAndrew Thompson 			sc->data_rem = 0;
35502ac6454SAndrew Thompson 		}
35602ac6454SAndrew Thompson 	case USB_ST_SETUP:
35702ac6454SAndrew Thompson 		DPRINTF("max_bulk=%d, data_rem=%d\n",
35802ac6454SAndrew Thompson 		    max_bulk, sc->data_rem);
35902ac6454SAndrew Thompson 
36002ac6454SAndrew Thompson 		if (sc->data_rem == 0) {
36102ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_STATUS);
36202ac6454SAndrew Thompson 			return;
36302ac6454SAndrew Thompson 		}
36402ac6454SAndrew Thompson 		if (max_bulk > sc->data_rem) {
36502ac6454SAndrew Thompson 			max_bulk = sc->data_rem;
36602ac6454SAndrew Thompson 		}
36702ac6454SAndrew Thompson 		xfer->timeout = sc->data_timeout;
36802ac6454SAndrew Thompson 		xfer->frlengths[0] = max_bulk;
36902ac6454SAndrew Thompson 
37002ac6454SAndrew Thompson 		usb2_set_frame_data(xfer, sc->data_ptr, 0);
37102ac6454SAndrew Thompson 		usb2_start_hardware(xfer);
37202ac6454SAndrew Thompson 		return;
37302ac6454SAndrew Thompson 
37402ac6454SAndrew Thompson 	default:			/* Error */
37502ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
37602ac6454SAndrew Thompson 			bbb_done(sc, 1);
37702ac6454SAndrew Thompson 		} else {
37802ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_WR_CS);
37902ac6454SAndrew Thompson 		}
38002ac6454SAndrew Thompson 		return;
38102ac6454SAndrew Thompson 
38202ac6454SAndrew Thompson 	}
38302ac6454SAndrew Thompson }
38402ac6454SAndrew Thompson 
38502ac6454SAndrew Thompson static void
38602ac6454SAndrew Thompson bbb_data_wr_cs_callback(struct usb2_xfer *xfer)
38702ac6454SAndrew Thompson {
38802ac6454SAndrew Thompson 	bbb_data_clear_stall_callback(xfer, ST_STATUS,
38902ac6454SAndrew Thompson 	    ST_DATA_WR);
39002ac6454SAndrew Thompson }
39102ac6454SAndrew Thompson 
39202ac6454SAndrew Thompson static void
39302ac6454SAndrew Thompson bbb_status_callback(struct usb2_xfer *xfer)
39402ac6454SAndrew Thompson {
39502ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
39602ac6454SAndrew Thompson 
39702ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
39802ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
39902ac6454SAndrew Thompson 
40002ac6454SAndrew Thompson 		/* very simple status check */
40102ac6454SAndrew Thompson 
40202ac6454SAndrew Thompson 		if (xfer->actlen < sizeof(sc->csw)) {
40302ac6454SAndrew Thompson 			bbb_done(sc, 1);/* error */
40402ac6454SAndrew Thompson 		} else if (sc->csw.bCSWStatus == CSWSTATUS_GOOD) {
40502ac6454SAndrew Thompson 			bbb_done(sc, 0);/* success */
40602ac6454SAndrew Thompson 		} else {
40702ac6454SAndrew Thompson 			bbb_done(sc, 1);/* error */
40802ac6454SAndrew Thompson 		}
40902ac6454SAndrew Thompson 		break;
41002ac6454SAndrew Thompson 
41102ac6454SAndrew Thompson 	case USB_ST_SETUP:
41202ac6454SAndrew Thompson 		xfer->frlengths[0] = sizeof(sc->csw);
41302ac6454SAndrew Thompson 
41402ac6454SAndrew Thompson 		usb2_set_frame_data(xfer, &sc->csw, 0);
41502ac6454SAndrew Thompson 		usb2_start_hardware(xfer);
41602ac6454SAndrew Thompson 		break;
41702ac6454SAndrew Thompson 
41802ac6454SAndrew Thompson 	default:
41902ac6454SAndrew Thompson 		DPRINTFN(0, "Failed to read CSW: %s, try %d\n",
42002ac6454SAndrew Thompson 		    usb2_errstr(xfer->error), sc->status_try);
42102ac6454SAndrew Thompson 
42202ac6454SAndrew Thompson 		if ((xfer->error == USB_ERR_CANCELLED) ||
42302ac6454SAndrew Thompson 		    (sc->status_try)) {
42402ac6454SAndrew Thompson 			bbb_done(sc, 1);
42502ac6454SAndrew Thompson 		} else {
42602ac6454SAndrew Thompson 			sc->status_try = 1;
42702ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_RD_CS);
42802ac6454SAndrew Thompson 		}
42902ac6454SAndrew Thompson 		break;
43002ac6454SAndrew Thompson 	}
43102ac6454SAndrew Thompson }
43202ac6454SAndrew Thompson 
43302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
43402ac6454SAndrew Thompson  *	bbb_command_start - execute a SCSI command synchronously
43502ac6454SAndrew Thompson  *
43602ac6454SAndrew Thompson  * Return values
43702ac6454SAndrew Thompson  * 0: Success
43802ac6454SAndrew Thompson  * Else: Failure
43902ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
44002ac6454SAndrew Thompson static uint8_t
44102ac6454SAndrew Thompson bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun,
44202ac6454SAndrew Thompson     void *data_ptr, uint32_t data_len, uint8_t cmd_len,
44302ac6454SAndrew Thompson     uint32_t data_timeout)
44402ac6454SAndrew Thompson {
44502ac6454SAndrew Thompson 	sc->lun = lun;
44602ac6454SAndrew Thompson 	sc->dir = data_len ? dir : DIR_NONE;
44702ac6454SAndrew Thompson 	sc->data_ptr = data_ptr;
44802ac6454SAndrew Thompson 	sc->data_len = data_len;
44902ac6454SAndrew Thompson 	sc->data_rem = data_len;
45002ac6454SAndrew Thompson 	sc->data_timeout = (data_timeout + USB_MS_HZ);
45102ac6454SAndrew Thompson 	sc->actlen = 0;
45202ac6454SAndrew Thompson 	sc->cmd_len = cmd_len;
45302ac6454SAndrew Thompson 
45402ac6454SAndrew Thompson 	usb2_transfer_start(sc->xfer[sc->state]);
45502ac6454SAndrew Thompson 
45602ac6454SAndrew Thompson 	while (usb2_transfer_pending(sc->xfer[sc->state])) {
45702ac6454SAndrew Thompson 		usb2_cv_wait(&sc->cv, &sc->mtx);
45802ac6454SAndrew Thompson 	}
45902ac6454SAndrew Thompson 	return (sc->error);
46002ac6454SAndrew Thompson }
46102ac6454SAndrew Thompson 
46202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
46302ac6454SAndrew Thompson  *	usb2_test_autoinstall
46402ac6454SAndrew Thompson  *
46502ac6454SAndrew Thompson  * Return values:
46602ac6454SAndrew Thompson  * 0: This interface is an auto install disk (CD-ROM)
46702ac6454SAndrew Thompson  * Else: Not an auto install disk.
46802ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
46902ac6454SAndrew Thompson usb2_error_t
47002ac6454SAndrew Thompson usb2_test_autoinstall(struct usb2_device *udev, uint8_t iface_index,
47102ac6454SAndrew Thompson     uint8_t do_eject)
47202ac6454SAndrew Thompson {
47302ac6454SAndrew Thompson 	struct usb2_interface *iface;
47402ac6454SAndrew Thompson 	struct usb2_interface_descriptor *id;
47502ac6454SAndrew Thompson 	usb2_error_t err;
47602ac6454SAndrew Thompson 	uint8_t timeout;
47702ac6454SAndrew Thompson 	uint8_t sid_type;
47802ac6454SAndrew Thompson 	struct bbb_transfer *sc;
47902ac6454SAndrew Thompson 
48002ac6454SAndrew Thompson 	if (udev == NULL) {
48102ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
48202ac6454SAndrew Thompson 	}
48302ac6454SAndrew Thompson 	iface = usb2_get_iface(udev, iface_index);
48402ac6454SAndrew Thompson 	if (iface == NULL) {
48502ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
48602ac6454SAndrew Thompson 	}
48702ac6454SAndrew Thompson 	id = iface->idesc;
48802ac6454SAndrew Thompson 	if (id == NULL) {
48902ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
49002ac6454SAndrew Thompson 	}
49102ac6454SAndrew Thompson 	if (id->bInterfaceClass != UICLASS_MASS) {
49202ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
49302ac6454SAndrew Thompson 	}
49402ac6454SAndrew Thompson 	switch (id->bInterfaceSubClass) {
49502ac6454SAndrew Thompson 	case UISUBCLASS_SCSI:
49602ac6454SAndrew Thompson 	case UISUBCLASS_UFI:
49702ac6454SAndrew Thompson 		break;
49802ac6454SAndrew Thompson 	default:
49902ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
50002ac6454SAndrew Thompson 	}
50102ac6454SAndrew Thompson 
50202ac6454SAndrew Thompson 	switch (id->bInterfaceProtocol) {
50302ac6454SAndrew Thompson 	case UIPROTO_MASS_BBB_OLD:
50402ac6454SAndrew Thompson 	case UIPROTO_MASS_BBB:
50502ac6454SAndrew Thompson 		break;
50602ac6454SAndrew Thompson 	default:
50702ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
50802ac6454SAndrew Thompson 	}
50902ac6454SAndrew Thompson 
51002ac6454SAndrew Thompson 	sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO);
51102ac6454SAndrew Thompson 	if (sc == NULL) {
51202ac6454SAndrew Thompson 		return (USB_ERR_NOMEM);
51302ac6454SAndrew Thompson 	}
51402ac6454SAndrew Thompson 	mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF);
51502ac6454SAndrew Thompson 	usb2_cv_init(&sc->cv, "WBBB");
51602ac6454SAndrew Thompson 
51702ac6454SAndrew Thompson 	err = usb2_transfer_setup(udev,
51802ac6454SAndrew Thompson 	    &iface_index, sc->xfer, bbb_config,
51902ac6454SAndrew Thompson 	    ST_MAX, sc, &sc->mtx);
52002ac6454SAndrew Thompson 
52102ac6454SAndrew Thompson 	if (err) {
52202ac6454SAndrew Thompson 		goto done;
52302ac6454SAndrew Thompson 	}
52402ac6454SAndrew Thompson 	mtx_lock(&sc->mtx);
52502ac6454SAndrew Thompson 
52602ac6454SAndrew Thompson 	timeout = 4;			/* tries */
52702ac6454SAndrew Thompson 
52802ac6454SAndrew Thompson repeat_inquiry:
52902ac6454SAndrew Thompson 
53002ac6454SAndrew Thompson 	sc->cbw.CBWCDB[0] = 0x12;	/* INQUIRY */
53102ac6454SAndrew Thompson 	sc->cbw.CBWCDB[1] = 0;
53202ac6454SAndrew Thompson 	sc->cbw.CBWCDB[2] = 0;
53302ac6454SAndrew Thompson 	sc->cbw.CBWCDB[3] = 0;
53402ac6454SAndrew Thompson 	sc->cbw.CBWCDB[4] = 0x24;	/* length */
53502ac6454SAndrew Thompson 	sc->cbw.CBWCDB[5] = 0;
53602ac6454SAndrew Thompson 	err = bbb_command_start(sc, DIR_IN, 0,
53702ac6454SAndrew Thompson 	    sc->buffer, 0x24, 6, USB_MS_HZ);
53802ac6454SAndrew Thompson 
53902ac6454SAndrew Thompson 	if ((sc->actlen != 0) && (err == 0)) {
54002ac6454SAndrew Thompson 		sid_type = sc->buffer[0] & 0x1F;
54102ac6454SAndrew Thompson 		if (sid_type == 0x05) {
54202ac6454SAndrew Thompson 			/* CD-ROM */
54302ac6454SAndrew Thompson 			if (do_eject) {
54402ac6454SAndrew Thompson 				/* 0: opcode: SCSI START/STOP */
54502ac6454SAndrew Thompson 				sc->cbw.CBWCDB[0] = 0x1b;
54602ac6454SAndrew Thompson 				/* 1: byte2: Not immediate */
54702ac6454SAndrew Thompson 				sc->cbw.CBWCDB[1] = 0x00;
54802ac6454SAndrew Thompson 				/* 2..3: reserved */
54902ac6454SAndrew Thompson 				sc->cbw.CBWCDB[2] = 0x00;
55002ac6454SAndrew Thompson 				sc->cbw.CBWCDB[3] = 0x00;
55102ac6454SAndrew Thompson 				/* 4: Load/Eject command */
55202ac6454SAndrew Thompson 				sc->cbw.CBWCDB[4] = 0x02;
55302ac6454SAndrew Thompson 				/* 5: control */
55402ac6454SAndrew Thompson 				sc->cbw.CBWCDB[5] = 0x00;
55502ac6454SAndrew Thompson 				err = bbb_command_start(sc, DIR_OUT, 0,
55602ac6454SAndrew Thompson 				    NULL, 0, 6, USB_MS_HZ);
55702ac6454SAndrew Thompson 
55802ac6454SAndrew Thompson 				DPRINTFN(0, "Eject CD command "
55902ac6454SAndrew Thompson 				    "status: %s\n", usb2_errstr(err));
56002ac6454SAndrew Thompson 			}
56102ac6454SAndrew Thompson 			err = 0;
56202ac6454SAndrew Thompson 			goto done;
56302ac6454SAndrew Thompson 		}
56402ac6454SAndrew Thompson 	} else if ((err != 2) && --timeout) {
56502ac6454SAndrew Thompson 		usb2_pause_mtx(&sc->mtx, hz);
56602ac6454SAndrew Thompson 		goto repeat_inquiry;
56702ac6454SAndrew Thompson 	}
56802ac6454SAndrew Thompson 	err = USB_ERR_INVAL;
56902ac6454SAndrew Thompson 	goto done;
57002ac6454SAndrew Thompson 
57102ac6454SAndrew Thompson done:
57202ac6454SAndrew Thompson 	mtx_unlock(&sc->mtx);
57302ac6454SAndrew Thompson 	usb2_transfer_unsetup(sc->xfer, ST_MAX);
57402ac6454SAndrew Thompson 	mtx_destroy(&sc->mtx);
57502ac6454SAndrew Thompson 	usb2_cv_destroy(&sc->cv);
57602ac6454SAndrew Thompson 	free(sc, M_USB);
57702ac6454SAndrew Thompson 	return (err);
57802ac6454SAndrew Thompson }
579