xref: /freebsd/sys/dev/usb/usb_msctest.c (revision a593f6b8de8b956bd0dd22f74805ede942e3d6a9)
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_mfunc.h>
3602ac6454SAndrew Thompson #include <dev/usb/usb_error.h>
3702ac6454SAndrew Thompson #include <dev/usb/usb.h>
3802ac6454SAndrew Thompson 
39a593f6b8SAndrew Thompson #define	USB_DEBUG_VAR usb_debug
4002ac6454SAndrew Thompson 
4102ac6454SAndrew Thompson #include <dev/usb/usb_core.h>
4202ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
4302ac6454SAndrew Thompson #include <dev/usb/usb_process.h>
4402ac6454SAndrew Thompson #include <dev/usb/usb_transfer.h>
4502ac6454SAndrew Thompson #include <dev/usb/usb_msctest.h>
4602ac6454SAndrew Thompson #include <dev/usb/usb_debug.h>
4702ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
4802ac6454SAndrew Thompson #include <dev/usb/usb_device.h>
4902ac6454SAndrew Thompson #include <dev/usb/usb_request.h>
5002ac6454SAndrew Thompson #include <dev/usb/usb_util.h>
5102ac6454SAndrew Thompson #include <dev/usb/usb_lookup.h>
5202ac6454SAndrew Thompson 
5302ac6454SAndrew Thompson #include <dev/usb/usb_mfunc.h>
5402ac6454SAndrew Thompson #include <dev/usb/usb_error.h>
5502ac6454SAndrew Thompson #include <dev/usb/usb.h>
5602ac6454SAndrew Thompson 
5702ac6454SAndrew Thompson enum {
5802ac6454SAndrew Thompson 	ST_COMMAND,
5902ac6454SAndrew Thompson 	ST_DATA_RD,
6002ac6454SAndrew Thompson 	ST_DATA_RD_CS,
6102ac6454SAndrew Thompson 	ST_DATA_WR,
6202ac6454SAndrew Thompson 	ST_DATA_WR_CS,
6302ac6454SAndrew Thompson 	ST_STATUS,
6402ac6454SAndrew Thompson 	ST_MAX,
6502ac6454SAndrew Thompson };
6602ac6454SAndrew Thompson 
6702ac6454SAndrew Thompson enum {
6802ac6454SAndrew Thompson 	DIR_IN,
6902ac6454SAndrew Thompson 	DIR_OUT,
7002ac6454SAndrew Thompson 	DIR_NONE,
7102ac6454SAndrew Thompson };
7202ac6454SAndrew Thompson 
7302ac6454SAndrew Thompson #define	BULK_SIZE		64	/* dummy */
7402ac6454SAndrew Thompson 
7502ac6454SAndrew Thompson /* Command Block Wrapper */
7602ac6454SAndrew Thompson struct bbb_cbw {
7702ac6454SAndrew Thompson 	uDWord	dCBWSignature;
7802ac6454SAndrew Thompson #define	CBWSIGNATURE	0x43425355
7902ac6454SAndrew Thompson 	uDWord	dCBWTag;
8002ac6454SAndrew Thompson 	uDWord	dCBWDataTransferLength;
8102ac6454SAndrew Thompson 	uByte	bCBWFlags;
8202ac6454SAndrew Thompson #define	CBWFLAGS_OUT	0x00
8302ac6454SAndrew Thompson #define	CBWFLAGS_IN	0x80
8402ac6454SAndrew Thompson 	uByte	bCBWLUN;
8502ac6454SAndrew Thompson 	uByte	bCDBLength;
8602ac6454SAndrew Thompson #define	CBWCDBLENGTH	16
8702ac6454SAndrew Thompson 	uByte	CBWCDB[CBWCDBLENGTH];
8802ac6454SAndrew Thompson } __packed;
8902ac6454SAndrew Thompson 
9002ac6454SAndrew Thompson /* Command Status Wrapper */
9102ac6454SAndrew Thompson struct bbb_csw {
9202ac6454SAndrew Thompson 	uDWord	dCSWSignature;
9302ac6454SAndrew Thompson #define	CSWSIGNATURE	0x53425355
9402ac6454SAndrew Thompson 	uDWord	dCSWTag;
9502ac6454SAndrew Thompson 	uDWord	dCSWDataResidue;
9602ac6454SAndrew Thompson 	uByte	bCSWStatus;
9702ac6454SAndrew Thompson #define	CSWSTATUS_GOOD	0x0
9802ac6454SAndrew Thompson #define	CSWSTATUS_FAILED	0x1
9902ac6454SAndrew Thompson #define	CSWSTATUS_PHASE	0x2
10002ac6454SAndrew Thompson } __packed;
10102ac6454SAndrew Thompson 
10202ac6454SAndrew Thompson struct bbb_transfer {
10302ac6454SAndrew Thompson 	struct mtx mtx;
10402ac6454SAndrew Thompson 	struct cv cv;
10502ac6454SAndrew Thompson 	struct bbb_cbw cbw;
10602ac6454SAndrew Thompson 	struct bbb_csw csw;
10702ac6454SAndrew Thompson 
108760bc48eSAndrew Thompson 	struct usb_xfer *xfer[ST_MAX];
10902ac6454SAndrew Thompson 
11002ac6454SAndrew Thompson 	uint8_t *data_ptr;
11102ac6454SAndrew Thompson 
112f9cb546cSAndrew Thompson 	usb_size_t data_len;		/* bytes */
113f9cb546cSAndrew Thompson 	usb_size_t data_rem;		/* bytes */
114e0a69b51SAndrew Thompson 	usb_timeout_t data_timeout;	/* ms */
115e0a69b51SAndrew Thompson 	usb_frlength_t actlen;		/* bytes */
11602ac6454SAndrew Thompson 
11702ac6454SAndrew Thompson 	uint8_t	cmd_len;		/* bytes */
11802ac6454SAndrew Thompson 	uint8_t	dir;
11902ac6454SAndrew Thompson 	uint8_t	lun;
12002ac6454SAndrew Thompson 	uint8_t	state;
12102ac6454SAndrew Thompson 	uint8_t	error;
12202ac6454SAndrew Thompson 	uint8_t	status_try;
12302ac6454SAndrew Thompson 
12402ac6454SAndrew Thompson 	uint8_t	buffer[256];
12502ac6454SAndrew Thompson };
12602ac6454SAndrew Thompson 
127e0a69b51SAndrew Thompson static usb_callback_t bbb_command_callback;
128e0a69b51SAndrew Thompson static usb_callback_t bbb_data_read_callback;
129e0a69b51SAndrew Thompson static usb_callback_t bbb_data_rd_cs_callback;
130e0a69b51SAndrew Thompson static usb_callback_t bbb_data_write_callback;
131e0a69b51SAndrew Thompson static usb_callback_t bbb_data_wr_cs_callback;
132e0a69b51SAndrew Thompson static usb_callback_t bbb_status_callback;
13302ac6454SAndrew Thompson 
134760bc48eSAndrew Thompson static const struct usb_config bbb_config[ST_MAX] = {
13502ac6454SAndrew Thompson 
13602ac6454SAndrew Thompson 	[ST_COMMAND] = {
13702ac6454SAndrew Thompson 		.type = UE_BULK,
13802ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
13902ac6454SAndrew Thompson 		.direction = UE_DIR_OUT,
1404eae601eSAndrew Thompson 		.bufsize = sizeof(struct bbb_cbw),
1414eae601eSAndrew Thompson 		.callback = &bbb_command_callback,
1424eae601eSAndrew Thompson 		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
14302ac6454SAndrew Thompson 	},
14402ac6454SAndrew Thompson 
14502ac6454SAndrew Thompson 	[ST_DATA_RD] = {
14602ac6454SAndrew Thompson 		.type = UE_BULK,
14702ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
14802ac6454SAndrew Thompson 		.direction = UE_DIR_IN,
1494eae601eSAndrew Thompson 		.bufsize = BULK_SIZE,
1504eae601eSAndrew Thompson 		.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,},
1514eae601eSAndrew Thompson 		.callback = &bbb_data_read_callback,
1524eae601eSAndrew Thompson 		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
15302ac6454SAndrew Thompson 	},
15402ac6454SAndrew Thompson 
15502ac6454SAndrew Thompson 	[ST_DATA_RD_CS] = {
15602ac6454SAndrew Thompson 		.type = UE_CONTROL,
15702ac6454SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
15802ac6454SAndrew Thompson 		.direction = UE_DIR_ANY,
159760bc48eSAndrew Thompson 		.bufsize = sizeof(struct usb_device_request),
1604eae601eSAndrew Thompson 		.callback = &bbb_data_rd_cs_callback,
1614eae601eSAndrew Thompson 		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
16202ac6454SAndrew Thompson 	},
16302ac6454SAndrew Thompson 
16402ac6454SAndrew Thompson 	[ST_DATA_WR] = {
16502ac6454SAndrew Thompson 		.type = UE_BULK,
16602ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
16702ac6454SAndrew Thompson 		.direction = UE_DIR_OUT,
1684eae601eSAndrew Thompson 		.bufsize = BULK_SIZE,
1694eae601eSAndrew Thompson 		.flags = {.proxy_buffer = 1,},
1704eae601eSAndrew Thompson 		.callback = &bbb_data_write_callback,
1714eae601eSAndrew Thompson 		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
17202ac6454SAndrew Thompson 	},
17302ac6454SAndrew Thompson 
17402ac6454SAndrew Thompson 	[ST_DATA_WR_CS] = {
17502ac6454SAndrew Thompson 		.type = UE_CONTROL,
17602ac6454SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
17702ac6454SAndrew Thompson 		.direction = UE_DIR_ANY,
178760bc48eSAndrew Thompson 		.bufsize = sizeof(struct usb_device_request),
1794eae601eSAndrew Thompson 		.callback = &bbb_data_wr_cs_callback,
1804eae601eSAndrew Thompson 		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
18102ac6454SAndrew Thompson 	},
18202ac6454SAndrew Thompson 
18302ac6454SAndrew Thompson 	[ST_STATUS] = {
18402ac6454SAndrew Thompson 		.type = UE_BULK,
18502ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
18602ac6454SAndrew Thompson 		.direction = UE_DIR_IN,
1874eae601eSAndrew Thompson 		.bufsize = sizeof(struct bbb_csw),
1884eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
1894eae601eSAndrew Thompson 		.callback = &bbb_status_callback,
1904eae601eSAndrew Thompson 		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
19102ac6454SAndrew Thompson 	},
19202ac6454SAndrew Thompson };
19302ac6454SAndrew Thompson 
19402ac6454SAndrew Thompson static void
19502ac6454SAndrew Thompson bbb_done(struct bbb_transfer *sc, uint8_t error)
19602ac6454SAndrew Thompson {
197760bc48eSAndrew Thompson 	struct usb_xfer *xfer;
19802ac6454SAndrew Thompson 
19902ac6454SAndrew Thompson 	xfer = sc->xfer[sc->state];
20002ac6454SAndrew Thompson 
20102ac6454SAndrew Thompson 	/* verify the error code */
20202ac6454SAndrew Thompson 
20302ac6454SAndrew Thompson 	if (error) {
20402ac6454SAndrew Thompson 		switch (USB_GET_STATE(xfer)) {
20502ac6454SAndrew Thompson 		case USB_ST_SETUP:
20602ac6454SAndrew Thompson 		case USB_ST_TRANSFERRED:
20702ac6454SAndrew Thompson 			error = 1;
20802ac6454SAndrew Thompson 			break;
20902ac6454SAndrew Thompson 		default:
21002ac6454SAndrew Thompson 			error = 2;
21102ac6454SAndrew Thompson 			break;
21202ac6454SAndrew Thompson 		}
21302ac6454SAndrew Thompson 	}
21402ac6454SAndrew Thompson 	sc->error = error;
21502ac6454SAndrew Thompson 	sc->state = ST_COMMAND;
21602ac6454SAndrew Thompson 	sc->status_try = 1;
2178437751dSAndrew Thompson 	cv_signal(&sc->cv);
21802ac6454SAndrew Thompson }
21902ac6454SAndrew Thompson 
22002ac6454SAndrew Thompson static void
22102ac6454SAndrew Thompson bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index)
22202ac6454SAndrew Thompson {
22302ac6454SAndrew Thompson 	sc->state = xfer_index;
224a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->xfer[xfer_index]);
22502ac6454SAndrew Thompson }
22602ac6454SAndrew Thompson 
22702ac6454SAndrew Thompson static void
228760bc48eSAndrew Thompson bbb_data_clear_stall_callback(struct usb_xfer *xfer,
22902ac6454SAndrew Thompson     uint8_t next_xfer, uint8_t stall_xfer)
23002ac6454SAndrew Thompson {
23102ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
23202ac6454SAndrew Thompson 
233a593f6b8SAndrew Thompson 	if (usbd_clear_stall_callback(xfer, sc->xfer[stall_xfer])) {
23402ac6454SAndrew Thompson 		switch (USB_GET_STATE(xfer)) {
23502ac6454SAndrew Thompson 		case USB_ST_SETUP:
23602ac6454SAndrew Thompson 		case USB_ST_TRANSFERRED:
23702ac6454SAndrew Thompson 			bbb_transfer_start(sc, next_xfer);
23802ac6454SAndrew Thompson 			break;
23902ac6454SAndrew Thompson 		default:
24002ac6454SAndrew Thompson 			bbb_done(sc, 1);
24102ac6454SAndrew Thompson 			break;
24202ac6454SAndrew Thompson 		}
24302ac6454SAndrew Thompson 	}
24402ac6454SAndrew Thompson }
24502ac6454SAndrew Thompson 
24602ac6454SAndrew Thompson static void
247760bc48eSAndrew Thompson bbb_command_callback(struct usb_xfer *xfer)
24802ac6454SAndrew Thompson {
24902ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
25002ac6454SAndrew Thompson 	uint32_t tag;
25102ac6454SAndrew Thompson 
25202ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
25302ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
25402ac6454SAndrew Thompson 		bbb_transfer_start
25502ac6454SAndrew Thompson 		    (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD :
25602ac6454SAndrew Thompson 		    (sc->dir == DIR_OUT) ? ST_DATA_WR :
25702ac6454SAndrew Thompson 		    ST_STATUS));
25802ac6454SAndrew Thompson 		break;
25902ac6454SAndrew Thompson 
26002ac6454SAndrew Thompson 	case USB_ST_SETUP:
26102ac6454SAndrew Thompson 		sc->status_try = 0;
26202ac6454SAndrew Thompson 		tag = UGETDW(sc->cbw.dCBWTag) + 1;
26302ac6454SAndrew Thompson 		USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE);
26402ac6454SAndrew Thompson 		USETDW(sc->cbw.dCBWTag, tag);
265578d0effSAndrew Thompson 		USETDW(sc->cbw.dCBWDataTransferLength, (uint32_t)sc->data_len);
26602ac6454SAndrew Thompson 		sc->cbw.bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT);
26702ac6454SAndrew Thompson 		sc->cbw.bCBWLUN = sc->lun;
26802ac6454SAndrew Thompson 		sc->cbw.bCDBLength = sc->cmd_len;
26902ac6454SAndrew Thompson 		if (sc->cbw.bCDBLength > sizeof(sc->cbw.CBWCDB)) {
27002ac6454SAndrew Thompson 			sc->cbw.bCDBLength = sizeof(sc->cbw.CBWCDB);
27102ac6454SAndrew Thompson 			DPRINTFN(0, "Truncating long command!\n");
27202ac6454SAndrew Thompson 		}
27302ac6454SAndrew Thompson 		xfer->frlengths[0] = sizeof(sc->cbw);
27402ac6454SAndrew Thompson 
275a593f6b8SAndrew Thompson 		usbd_set_frame_data(xfer, &sc->cbw, 0);
276a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
27702ac6454SAndrew Thompson 		break;
27802ac6454SAndrew Thompson 
27902ac6454SAndrew Thompson 	default:			/* Error */
28002ac6454SAndrew Thompson 		bbb_done(sc, 1);
28102ac6454SAndrew Thompson 		break;
28202ac6454SAndrew Thompson 	}
28302ac6454SAndrew Thompson }
28402ac6454SAndrew Thompson 
28502ac6454SAndrew Thompson static void
286760bc48eSAndrew Thompson bbb_data_read_callback(struct usb_xfer *xfer)
28702ac6454SAndrew Thompson {
28802ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
289e0a69b51SAndrew Thompson 	usb_frlength_t max_bulk = xfer->max_data_length;
29002ac6454SAndrew Thompson 
29102ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
29202ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
29302ac6454SAndrew Thompson 		sc->data_rem -= xfer->actlen;
29402ac6454SAndrew Thompson 		sc->data_ptr += xfer->actlen;
29502ac6454SAndrew Thompson 		sc->actlen += xfer->actlen;
29602ac6454SAndrew Thompson 
29702ac6454SAndrew Thompson 		if (xfer->actlen < xfer->sumlen) {
29802ac6454SAndrew Thompson 			/* short transfer */
29902ac6454SAndrew Thompson 			sc->data_rem = 0;
30002ac6454SAndrew Thompson 		}
30102ac6454SAndrew Thompson 	case USB_ST_SETUP:
30202ac6454SAndrew Thompson 		DPRINTF("max_bulk=%d, data_rem=%d\n",
30302ac6454SAndrew Thompson 		    max_bulk, sc->data_rem);
30402ac6454SAndrew Thompson 
30502ac6454SAndrew Thompson 		if (sc->data_rem == 0) {
30602ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_STATUS);
30702ac6454SAndrew Thompson 			break;
30802ac6454SAndrew Thompson 		}
30902ac6454SAndrew Thompson 		if (max_bulk > sc->data_rem) {
31002ac6454SAndrew Thompson 			max_bulk = sc->data_rem;
31102ac6454SAndrew Thompson 		}
31202ac6454SAndrew Thompson 		xfer->timeout = sc->data_timeout;
31302ac6454SAndrew Thompson 		xfer->frlengths[0] = max_bulk;
31402ac6454SAndrew Thompson 
315a593f6b8SAndrew Thompson 		usbd_set_frame_data(xfer, sc->data_ptr, 0);
316a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
31702ac6454SAndrew Thompson 		break;
31802ac6454SAndrew Thompson 
31902ac6454SAndrew Thompson 	default:			/* Error */
32002ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
32102ac6454SAndrew Thompson 			bbb_done(sc, 1);
32202ac6454SAndrew Thompson 		} else {
32302ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_RD_CS);
32402ac6454SAndrew Thompson 		}
32502ac6454SAndrew Thompson 		break;
32602ac6454SAndrew Thompson 	}
32702ac6454SAndrew Thompson }
32802ac6454SAndrew Thompson 
32902ac6454SAndrew Thompson static void
330760bc48eSAndrew Thompson bbb_data_rd_cs_callback(struct usb_xfer *xfer)
33102ac6454SAndrew Thompson {
33202ac6454SAndrew Thompson 	bbb_data_clear_stall_callback(xfer, ST_STATUS,
33302ac6454SAndrew Thompson 	    ST_DATA_RD);
33402ac6454SAndrew Thompson }
33502ac6454SAndrew Thompson 
33602ac6454SAndrew Thompson static void
337760bc48eSAndrew Thompson bbb_data_write_callback(struct usb_xfer *xfer)
33802ac6454SAndrew Thompson {
33902ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
340e0a69b51SAndrew Thompson 	usb_frlength_t max_bulk = xfer->max_data_length;
34102ac6454SAndrew Thompson 
34202ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
34302ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
34402ac6454SAndrew Thompson 		sc->data_rem -= xfer->actlen;
34502ac6454SAndrew Thompson 		sc->data_ptr += xfer->actlen;
34602ac6454SAndrew Thompson 		sc->actlen += xfer->actlen;
34702ac6454SAndrew Thompson 
34802ac6454SAndrew Thompson 		if (xfer->actlen < xfer->sumlen) {
34902ac6454SAndrew Thompson 			/* short transfer */
35002ac6454SAndrew Thompson 			sc->data_rem = 0;
35102ac6454SAndrew Thompson 		}
35202ac6454SAndrew Thompson 	case USB_ST_SETUP:
35302ac6454SAndrew Thompson 		DPRINTF("max_bulk=%d, data_rem=%d\n",
35402ac6454SAndrew Thompson 		    max_bulk, sc->data_rem);
35502ac6454SAndrew Thompson 
35602ac6454SAndrew Thompson 		if (sc->data_rem == 0) {
35702ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_STATUS);
35802ac6454SAndrew Thompson 			return;
35902ac6454SAndrew Thompson 		}
36002ac6454SAndrew Thompson 		if (max_bulk > sc->data_rem) {
36102ac6454SAndrew Thompson 			max_bulk = sc->data_rem;
36202ac6454SAndrew Thompson 		}
36302ac6454SAndrew Thompson 		xfer->timeout = sc->data_timeout;
36402ac6454SAndrew Thompson 		xfer->frlengths[0] = max_bulk;
36502ac6454SAndrew Thompson 
366a593f6b8SAndrew Thompson 		usbd_set_frame_data(xfer, sc->data_ptr, 0);
367a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
36802ac6454SAndrew Thompson 		return;
36902ac6454SAndrew Thompson 
37002ac6454SAndrew Thompson 	default:			/* Error */
37102ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
37202ac6454SAndrew Thompson 			bbb_done(sc, 1);
37302ac6454SAndrew Thompson 		} else {
37402ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_WR_CS);
37502ac6454SAndrew Thompson 		}
37602ac6454SAndrew Thompson 		return;
37702ac6454SAndrew Thompson 
37802ac6454SAndrew Thompson 	}
37902ac6454SAndrew Thompson }
38002ac6454SAndrew Thompson 
38102ac6454SAndrew Thompson static void
382760bc48eSAndrew Thompson bbb_data_wr_cs_callback(struct usb_xfer *xfer)
38302ac6454SAndrew Thompson {
38402ac6454SAndrew Thompson 	bbb_data_clear_stall_callback(xfer, ST_STATUS,
38502ac6454SAndrew Thompson 	    ST_DATA_WR);
38602ac6454SAndrew Thompson }
38702ac6454SAndrew Thompson 
38802ac6454SAndrew Thompson static void
389760bc48eSAndrew Thompson bbb_status_callback(struct usb_xfer *xfer)
39002ac6454SAndrew Thompson {
39102ac6454SAndrew Thompson 	struct bbb_transfer *sc = xfer->priv_sc;
39202ac6454SAndrew Thompson 
39302ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
39402ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
39502ac6454SAndrew Thompson 
39602ac6454SAndrew Thompson 		/* very simple status check */
39702ac6454SAndrew Thompson 
39802ac6454SAndrew Thompson 		if (xfer->actlen < sizeof(sc->csw)) {
39902ac6454SAndrew Thompson 			bbb_done(sc, 1);/* error */
40002ac6454SAndrew Thompson 		} else if (sc->csw.bCSWStatus == CSWSTATUS_GOOD) {
40102ac6454SAndrew Thompson 			bbb_done(sc, 0);/* success */
40202ac6454SAndrew Thompson 		} else {
40302ac6454SAndrew Thompson 			bbb_done(sc, 1);/* error */
40402ac6454SAndrew Thompson 		}
40502ac6454SAndrew Thompson 		break;
40602ac6454SAndrew Thompson 
40702ac6454SAndrew Thompson 	case USB_ST_SETUP:
40802ac6454SAndrew Thompson 		xfer->frlengths[0] = sizeof(sc->csw);
40902ac6454SAndrew Thompson 
410a593f6b8SAndrew Thompson 		usbd_set_frame_data(xfer, &sc->csw, 0);
411a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
41202ac6454SAndrew Thompson 		break;
41302ac6454SAndrew Thompson 
41402ac6454SAndrew Thompson 	default:
41502ac6454SAndrew Thompson 		DPRINTFN(0, "Failed to read CSW: %s, try %d\n",
416a593f6b8SAndrew Thompson 		    usbd_errstr(xfer->error), sc->status_try);
41702ac6454SAndrew Thompson 
41802ac6454SAndrew Thompson 		if ((xfer->error == USB_ERR_CANCELLED) ||
41902ac6454SAndrew Thompson 		    (sc->status_try)) {
42002ac6454SAndrew Thompson 			bbb_done(sc, 1);
42102ac6454SAndrew Thompson 		} else {
42202ac6454SAndrew Thompson 			sc->status_try = 1;
42302ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_RD_CS);
42402ac6454SAndrew Thompson 		}
42502ac6454SAndrew Thompson 		break;
42602ac6454SAndrew Thompson 	}
42702ac6454SAndrew Thompson }
42802ac6454SAndrew Thompson 
42902ac6454SAndrew Thompson /*------------------------------------------------------------------------*
43002ac6454SAndrew Thompson  *	bbb_command_start - execute a SCSI command synchronously
43102ac6454SAndrew Thompson  *
43202ac6454SAndrew Thompson  * Return values
43302ac6454SAndrew Thompson  * 0: Success
43402ac6454SAndrew Thompson  * Else: Failure
43502ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
43602ac6454SAndrew Thompson static uint8_t
43702ac6454SAndrew Thompson bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun,
438f9cb546cSAndrew Thompson     void *data_ptr, usb_size_t data_len, uint8_t cmd_len,
439e0a69b51SAndrew Thompson     usb_timeout_t data_timeout)
44002ac6454SAndrew Thompson {
44102ac6454SAndrew Thompson 	sc->lun = lun;
44202ac6454SAndrew Thompson 	sc->dir = data_len ? dir : DIR_NONE;
44302ac6454SAndrew Thompson 	sc->data_ptr = data_ptr;
44402ac6454SAndrew Thompson 	sc->data_len = data_len;
44502ac6454SAndrew Thompson 	sc->data_rem = data_len;
44602ac6454SAndrew Thompson 	sc->data_timeout = (data_timeout + USB_MS_HZ);
44702ac6454SAndrew Thompson 	sc->actlen = 0;
44802ac6454SAndrew Thompson 	sc->cmd_len = cmd_len;
44902ac6454SAndrew Thompson 
450a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->xfer[sc->state]);
45102ac6454SAndrew Thompson 
452a593f6b8SAndrew Thompson 	while (usbd_transfer_pending(sc->xfer[sc->state])) {
4538437751dSAndrew Thompson 		cv_wait(&sc->cv, &sc->mtx);
45402ac6454SAndrew Thompson 	}
45502ac6454SAndrew Thompson 	return (sc->error);
45602ac6454SAndrew Thompson }
45702ac6454SAndrew Thompson 
45802ac6454SAndrew Thompson /*------------------------------------------------------------------------*
459a593f6b8SAndrew Thompson  *	usb_test_autoinstall
46002ac6454SAndrew Thompson  *
46102ac6454SAndrew Thompson  * Return values:
46202ac6454SAndrew Thompson  * 0: This interface is an auto install disk (CD-ROM)
46302ac6454SAndrew Thompson  * Else: Not an auto install disk.
46402ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
465e0a69b51SAndrew Thompson usb_error_t
466a593f6b8SAndrew Thompson usb_test_autoinstall(struct usb_device *udev, uint8_t iface_index,
46702ac6454SAndrew Thompson     uint8_t do_eject)
46802ac6454SAndrew Thompson {
469760bc48eSAndrew Thompson 	struct usb_interface *iface;
470760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
471e0a69b51SAndrew Thompson 	usb_error_t err;
47202ac6454SAndrew Thompson 	uint8_t timeout;
47302ac6454SAndrew Thompson 	uint8_t sid_type;
47402ac6454SAndrew Thompson 	struct bbb_transfer *sc;
47502ac6454SAndrew Thompson 
47602ac6454SAndrew Thompson 	if (udev == NULL) {
47702ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
47802ac6454SAndrew Thompson 	}
479a593f6b8SAndrew Thompson 	iface = usbd_get_iface(udev, iface_index);
48002ac6454SAndrew Thompson 	if (iface == NULL) {
48102ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
48202ac6454SAndrew Thompson 	}
48302ac6454SAndrew Thompson 	id = iface->idesc;
48402ac6454SAndrew Thompson 	if (id == NULL) {
48502ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
48602ac6454SAndrew Thompson 	}
48702ac6454SAndrew Thompson 	if (id->bInterfaceClass != UICLASS_MASS) {
48802ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
48902ac6454SAndrew Thompson 	}
49002ac6454SAndrew Thompson 	switch (id->bInterfaceSubClass) {
49102ac6454SAndrew Thompson 	case UISUBCLASS_SCSI:
49202ac6454SAndrew Thompson 	case UISUBCLASS_UFI:
49302ac6454SAndrew Thompson 		break;
49402ac6454SAndrew Thompson 	default:
49502ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
49602ac6454SAndrew Thompson 	}
49702ac6454SAndrew Thompson 
49802ac6454SAndrew Thompson 	switch (id->bInterfaceProtocol) {
49902ac6454SAndrew Thompson 	case UIPROTO_MASS_BBB_OLD:
50002ac6454SAndrew Thompson 	case UIPROTO_MASS_BBB:
50102ac6454SAndrew Thompson 		break;
50202ac6454SAndrew Thompson 	default:
50302ac6454SAndrew Thompson 		return (USB_ERR_INVAL);
50402ac6454SAndrew Thompson 	}
50502ac6454SAndrew Thompson 
50602ac6454SAndrew Thompson 	sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO);
50702ac6454SAndrew Thompson 	if (sc == NULL) {
50802ac6454SAndrew Thompson 		return (USB_ERR_NOMEM);
50902ac6454SAndrew Thompson 	}
51002ac6454SAndrew Thompson 	mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF);
5118437751dSAndrew Thompson 	cv_init(&sc->cv, "WBBB");
51202ac6454SAndrew Thompson 
513a593f6b8SAndrew Thompson 	err = usbd_transfer_setup(udev,
51402ac6454SAndrew Thompson 	    &iface_index, sc->xfer, bbb_config,
51502ac6454SAndrew Thompson 	    ST_MAX, sc, &sc->mtx);
51602ac6454SAndrew Thompson 
51702ac6454SAndrew Thompson 	if (err) {
51802ac6454SAndrew Thompson 		goto done;
51902ac6454SAndrew Thompson 	}
52002ac6454SAndrew Thompson 	mtx_lock(&sc->mtx);
52102ac6454SAndrew Thompson 
52202ac6454SAndrew Thompson 	timeout = 4;			/* tries */
52302ac6454SAndrew Thompson 
52402ac6454SAndrew Thompson repeat_inquiry:
52502ac6454SAndrew Thompson 
52602ac6454SAndrew Thompson 	sc->cbw.CBWCDB[0] = 0x12;	/* INQUIRY */
52702ac6454SAndrew Thompson 	sc->cbw.CBWCDB[1] = 0;
52802ac6454SAndrew Thompson 	sc->cbw.CBWCDB[2] = 0;
52902ac6454SAndrew Thompson 	sc->cbw.CBWCDB[3] = 0;
53002ac6454SAndrew Thompson 	sc->cbw.CBWCDB[4] = 0x24;	/* length */
53102ac6454SAndrew Thompson 	sc->cbw.CBWCDB[5] = 0;
53202ac6454SAndrew Thompson 	err = bbb_command_start(sc, DIR_IN, 0,
53302ac6454SAndrew Thompson 	    sc->buffer, 0x24, 6, USB_MS_HZ);
53402ac6454SAndrew Thompson 
53502ac6454SAndrew Thompson 	if ((sc->actlen != 0) && (err == 0)) {
53602ac6454SAndrew Thompson 		sid_type = sc->buffer[0] & 0x1F;
53702ac6454SAndrew Thompson 		if (sid_type == 0x05) {
53802ac6454SAndrew Thompson 			/* CD-ROM */
53902ac6454SAndrew Thompson 			if (do_eject) {
54002ac6454SAndrew Thompson 				/* 0: opcode: SCSI START/STOP */
54102ac6454SAndrew Thompson 				sc->cbw.CBWCDB[0] = 0x1b;
54202ac6454SAndrew Thompson 				/* 1: byte2: Not immediate */
54302ac6454SAndrew Thompson 				sc->cbw.CBWCDB[1] = 0x00;
54402ac6454SAndrew Thompson 				/* 2..3: reserved */
54502ac6454SAndrew Thompson 				sc->cbw.CBWCDB[2] = 0x00;
54602ac6454SAndrew Thompson 				sc->cbw.CBWCDB[3] = 0x00;
54702ac6454SAndrew Thompson 				/* 4: Load/Eject command */
54802ac6454SAndrew Thompson 				sc->cbw.CBWCDB[4] = 0x02;
54902ac6454SAndrew Thompson 				/* 5: control */
55002ac6454SAndrew Thompson 				sc->cbw.CBWCDB[5] = 0x00;
55102ac6454SAndrew Thompson 				err = bbb_command_start(sc, DIR_OUT, 0,
55202ac6454SAndrew Thompson 				    NULL, 0, 6, USB_MS_HZ);
55302ac6454SAndrew Thompson 
55402ac6454SAndrew Thompson 				DPRINTFN(0, "Eject CD command "
555a593f6b8SAndrew Thompson 				    "status: %s\n", usbd_errstr(err));
55602ac6454SAndrew Thompson 			}
55702ac6454SAndrew Thompson 			err = 0;
55802ac6454SAndrew Thompson 			goto done;
55902ac6454SAndrew Thompson 		}
56002ac6454SAndrew Thompson 	} else if ((err != 2) && --timeout) {
561a593f6b8SAndrew Thompson 		usb_pause_mtx(&sc->mtx, hz);
56202ac6454SAndrew Thompson 		goto repeat_inquiry;
56302ac6454SAndrew Thompson 	}
56402ac6454SAndrew Thompson 	err = USB_ERR_INVAL;
56502ac6454SAndrew Thompson 	goto done;
56602ac6454SAndrew Thompson 
56702ac6454SAndrew Thompson done:
56802ac6454SAndrew Thompson 	mtx_unlock(&sc->mtx);
569a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(sc->xfer, ST_MAX);
57002ac6454SAndrew Thompson 	mtx_destroy(&sc->mtx);
5718437751dSAndrew Thompson 	cv_destroy(&sc->cv);
57202ac6454SAndrew Thompson 	free(sc, M_USB);
57302ac6454SAndrew Thompson 	return (err);
57402ac6454SAndrew Thompson }
575