xref: /freebsd/sys/dev/usb/usb_msctest.c (revision 0cb5abc7ca15f48d1d3058c4ee2912ddf3990a49)
102ac6454SAndrew Thompson /* $FreeBSD$ */
202ac6454SAndrew Thompson /*-
3d334432fSHans Petter Selasky  * Copyright (c) 2008,2011 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 
35ed6d949aSAndrew Thompson #include <sys/stdint.h>
36ed6d949aSAndrew Thompson #include <sys/stddef.h>
37ed6d949aSAndrew Thompson #include <sys/param.h>
38ed6d949aSAndrew Thompson #include <sys/queue.h>
39ed6d949aSAndrew Thompson #include <sys/types.h>
40ed6d949aSAndrew Thompson #include <sys/systm.h>
41ed6d949aSAndrew Thompson #include <sys/kernel.h>
42ed6d949aSAndrew Thompson #include <sys/bus.h>
43ed6d949aSAndrew Thompson #include <sys/module.h>
44ed6d949aSAndrew Thompson #include <sys/lock.h>
45ed6d949aSAndrew Thompson #include <sys/mutex.h>
46ed6d949aSAndrew Thompson #include <sys/condvar.h>
47ed6d949aSAndrew Thompson #include <sys/sysctl.h>
48ed6d949aSAndrew Thompson #include <sys/sx.h>
49ed6d949aSAndrew Thompson #include <sys/unistd.h>
50ed6d949aSAndrew Thompson #include <sys/callout.h>
51ed6d949aSAndrew Thompson #include <sys/malloc.h>
52ed6d949aSAndrew Thompson #include <sys/priv.h>
53ed6d949aSAndrew Thompson 
5402ac6454SAndrew Thompson #include <dev/usb/usb.h>
55ed6d949aSAndrew Thompson #include <dev/usb/usbdi.h>
56ed6d949aSAndrew Thompson #include <dev/usb/usbdi_util.h>
5702ac6454SAndrew Thompson 
58a593f6b8SAndrew Thompson #define	USB_DEBUG_VAR usb_debug
5902ac6454SAndrew Thompson 
6002ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
6102ac6454SAndrew Thompson #include <dev/usb/usb_process.h>
6202ac6454SAndrew Thompson #include <dev/usb/usb_transfer.h>
6302ac6454SAndrew Thompson #include <dev/usb/usb_msctest.h>
6402ac6454SAndrew Thompson #include <dev/usb/usb_debug.h>
6502ac6454SAndrew Thompson #include <dev/usb/usb_device.h>
6602ac6454SAndrew Thompson #include <dev/usb/usb_request.h>
6702ac6454SAndrew Thompson #include <dev/usb/usb_util.h>
68d84a79e7SAndrew Thompson #include <dev/usb/quirk/usb_quirk.h>
6902ac6454SAndrew Thompson 
7002ac6454SAndrew Thompson enum {
7102ac6454SAndrew Thompson 	ST_COMMAND,
7202ac6454SAndrew Thompson 	ST_DATA_RD,
7302ac6454SAndrew Thompson 	ST_DATA_RD_CS,
7402ac6454SAndrew Thompson 	ST_DATA_WR,
7502ac6454SAndrew Thompson 	ST_DATA_WR_CS,
7602ac6454SAndrew Thompson 	ST_STATUS,
7702ac6454SAndrew Thompson 	ST_MAX,
7802ac6454SAndrew Thompson };
7902ac6454SAndrew Thompson 
8002ac6454SAndrew Thompson enum {
8102ac6454SAndrew Thompson 	DIR_IN,
8202ac6454SAndrew Thompson 	DIR_OUT,
8302ac6454SAndrew Thompson 	DIR_NONE,
8402ac6454SAndrew Thompson };
8502ac6454SAndrew Thompson 
86d334432fSHans Petter Selasky #define	SCSI_MAX_LEN	0x100
87d84a79e7SAndrew Thompson #define	SCSI_INQ_LEN	0x24
88d334432fSHans Petter Selasky #define	SCSI_SENSE_LEN	0xFF
89d334432fSHans Petter Selasky 
90d84a79e7SAndrew Thompson static uint8_t scsi_test_unit_ready[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
91d84a79e7SAndrew Thompson static uint8_t scsi_inquiry[] = { 0x12, 0x00, 0x00, 0x00, SCSI_INQ_LEN, 0x00 };
92d84a79e7SAndrew Thompson static uint8_t scsi_rezero_init[] =     { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
93d84a79e7SAndrew Thompson static uint8_t scsi_start_stop_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00 };
94d84a79e7SAndrew Thompson static uint8_t scsi_ztestor_eject[] =   { 0x85, 0x01, 0x01, 0x01, 0x18, 0x01,
95d84a79e7SAndrew Thompson 					  0x01, 0x01, 0x01, 0x01, 0x00, 0x00 };
96d84a79e7SAndrew Thompson static uint8_t scsi_cmotech_eject[] =   { 0xff, 0x52, 0x44, 0x45, 0x56, 0x43,
97d84a79e7SAndrew Thompson 					  0x48, 0x47 };
982386d714SAndrew Thompson static uint8_t scsi_huawei_eject[] =	{ 0x11, 0x06, 0x00, 0x00, 0x00, 0x00,
992386d714SAndrew Thompson 					  0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1002386d714SAndrew Thompson 					  0x00, 0x00, 0x00, 0x00 };
101fb7a4d49SGleb Smirnoff static uint8_t scsi_tct_eject[] =	{ 0x06, 0xf5, 0x04, 0x02, 0x52, 0x70 };
102d46dc4adSHans Petter Selasky static uint8_t scsi_sync_cache[] =	{ 0x35, 0x00, 0x00, 0x00, 0x00, 0x00,
103d46dc4adSHans Petter Selasky 					  0x00, 0x00, 0x00, 0x00 };
104d334432fSHans Petter Selasky static uint8_t scsi_request_sense[] =	{ 0x03, 0x00, 0x00, 0x00, 0x12, 0x00,
105d334432fSHans Petter Selasky 					  0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
106*0cb5abc7SHans Petter Selasky static uint8_t scsi_read_capacity[] =	{ 0x25, 0x00, 0x00, 0x00, 0x00, 0x00,
107*0cb5abc7SHans Petter Selasky 					  0x00, 0x00, 0x00, 0x00 };
108d84a79e7SAndrew Thompson 
10902ac6454SAndrew Thompson #define	BULK_SIZE		64	/* dummy */
110d84a79e7SAndrew Thompson #define	ERR_CSW_FAILED		-1
11102ac6454SAndrew Thompson 
11202ac6454SAndrew Thompson /* Command Block Wrapper */
11302ac6454SAndrew Thompson struct bbb_cbw {
11402ac6454SAndrew Thompson 	uDWord	dCBWSignature;
11502ac6454SAndrew Thompson #define	CBWSIGNATURE	0x43425355
11602ac6454SAndrew Thompson 	uDWord	dCBWTag;
11702ac6454SAndrew Thompson 	uDWord	dCBWDataTransferLength;
11802ac6454SAndrew Thompson 	uByte	bCBWFlags;
11902ac6454SAndrew Thompson #define	CBWFLAGS_OUT	0x00
12002ac6454SAndrew Thompson #define	CBWFLAGS_IN	0x80
12102ac6454SAndrew Thompson 	uByte	bCBWLUN;
12202ac6454SAndrew Thompson 	uByte	bCDBLength;
12302ac6454SAndrew Thompson #define	CBWCDBLENGTH	16
12402ac6454SAndrew Thompson 	uByte	CBWCDB[CBWCDBLENGTH];
12502ac6454SAndrew Thompson } __packed;
12602ac6454SAndrew Thompson 
12702ac6454SAndrew Thompson /* Command Status Wrapper */
12802ac6454SAndrew Thompson struct bbb_csw {
12902ac6454SAndrew Thompson 	uDWord	dCSWSignature;
13002ac6454SAndrew Thompson #define	CSWSIGNATURE	0x53425355
13102ac6454SAndrew Thompson 	uDWord	dCSWTag;
13202ac6454SAndrew Thompson 	uDWord	dCSWDataResidue;
13302ac6454SAndrew Thompson 	uByte	bCSWStatus;
13402ac6454SAndrew Thompson #define	CSWSTATUS_GOOD	0x0
13502ac6454SAndrew Thompson #define	CSWSTATUS_FAILED	0x1
13602ac6454SAndrew Thompson #define	CSWSTATUS_PHASE	0x2
13702ac6454SAndrew Thompson } __packed;
13802ac6454SAndrew Thompson 
13902ac6454SAndrew Thompson struct bbb_transfer {
14002ac6454SAndrew Thompson 	struct mtx mtx;
14102ac6454SAndrew Thompson 	struct cv cv;
14202ac6454SAndrew Thompson 	struct bbb_cbw cbw;
14302ac6454SAndrew Thompson 	struct bbb_csw csw;
14402ac6454SAndrew Thompson 
145760bc48eSAndrew Thompson 	struct usb_xfer *xfer[ST_MAX];
14602ac6454SAndrew Thompson 
14702ac6454SAndrew Thompson 	uint8_t *data_ptr;
14802ac6454SAndrew Thompson 
149f9cb546cSAndrew Thompson 	usb_size_t data_len;		/* bytes */
150f9cb546cSAndrew Thompson 	usb_size_t data_rem;		/* bytes */
151e0a69b51SAndrew Thompson 	usb_timeout_t data_timeout;	/* ms */
152e0a69b51SAndrew Thompson 	usb_frlength_t actlen;		/* bytes */
15302ac6454SAndrew Thompson 
15402ac6454SAndrew Thompson 	uint8_t	cmd_len;		/* bytes */
15502ac6454SAndrew Thompson 	uint8_t	dir;
15602ac6454SAndrew Thompson 	uint8_t	lun;
15702ac6454SAndrew Thompson 	uint8_t	state;
15802ac6454SAndrew Thompson 	uint8_t	status_try;
159d84a79e7SAndrew Thompson 	int	error;
16002ac6454SAndrew Thompson 
161d334432fSHans Petter Selasky 	uint8_t	buffer[SCSI_MAX_LEN] __aligned(4);
16202ac6454SAndrew Thompson };
16302ac6454SAndrew Thompson 
164e0a69b51SAndrew Thompson static usb_callback_t bbb_command_callback;
165e0a69b51SAndrew Thompson static usb_callback_t bbb_data_read_callback;
166e0a69b51SAndrew Thompson static usb_callback_t bbb_data_rd_cs_callback;
167e0a69b51SAndrew Thompson static usb_callback_t bbb_data_write_callback;
168e0a69b51SAndrew Thompson static usb_callback_t bbb_data_wr_cs_callback;
169e0a69b51SAndrew Thompson static usb_callback_t bbb_status_callback;
17002ac6454SAndrew Thompson 
171d84a79e7SAndrew Thompson static void	bbb_done(struct bbb_transfer *, int);
172d84a79e7SAndrew Thompson static void	bbb_transfer_start(struct bbb_transfer *, uint8_t);
173d84a79e7SAndrew Thompson static void	bbb_data_clear_stall_callback(struct usb_xfer *, uint8_t,
174d84a79e7SAndrew Thompson 		    uint8_t);
175d46dc4adSHans Petter Selasky static int	bbb_command_start(struct bbb_transfer *, uint8_t, uint8_t,
176d84a79e7SAndrew Thompson 		    void *, size_t, void *, size_t, usb_timeout_t);
177d84a79e7SAndrew Thompson static struct bbb_transfer *bbb_attach(struct usb_device *, uint8_t);
178d84a79e7SAndrew Thompson static void	bbb_detach(struct bbb_transfer *);
179d84a79e7SAndrew Thompson 
180760bc48eSAndrew Thompson static const struct usb_config bbb_config[ST_MAX] = {
18102ac6454SAndrew Thompson 
18202ac6454SAndrew Thompson 	[ST_COMMAND] = {
18302ac6454SAndrew Thompson 		.type = UE_BULK,
18402ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
18502ac6454SAndrew Thompson 		.direction = UE_DIR_OUT,
1864eae601eSAndrew Thompson 		.bufsize = sizeof(struct bbb_cbw),
1877cd72d85SHans Petter Selasky 		.flags = {.ext_buffer = 1,},
1884eae601eSAndrew Thompson 		.callback = &bbb_command_callback,
1894eae601eSAndrew Thompson 		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
19002ac6454SAndrew Thompson 	},
19102ac6454SAndrew Thompson 
19202ac6454SAndrew Thompson 	[ST_DATA_RD] = {
19302ac6454SAndrew Thompson 		.type = UE_BULK,
19402ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
19502ac6454SAndrew Thompson 		.direction = UE_DIR_IN,
1964eae601eSAndrew Thompson 		.bufsize = BULK_SIZE,
1977cd72d85SHans Petter Selasky 		.flags = {.ext_buffer = 1,.proxy_buffer = 1,.short_xfer_ok = 1,},
1984eae601eSAndrew Thompson 		.callback = &bbb_data_read_callback,
1994eae601eSAndrew Thompson 		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
20002ac6454SAndrew Thompson 	},
20102ac6454SAndrew Thompson 
20202ac6454SAndrew Thompson 	[ST_DATA_RD_CS] = {
20302ac6454SAndrew Thompson 		.type = UE_CONTROL,
20402ac6454SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
20502ac6454SAndrew Thompson 		.direction = UE_DIR_ANY,
206760bc48eSAndrew Thompson 		.bufsize = sizeof(struct usb_device_request),
2074eae601eSAndrew Thompson 		.callback = &bbb_data_rd_cs_callback,
2084eae601eSAndrew Thompson 		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
20902ac6454SAndrew Thompson 	},
21002ac6454SAndrew Thompson 
21102ac6454SAndrew Thompson 	[ST_DATA_WR] = {
21202ac6454SAndrew Thompson 		.type = UE_BULK,
21302ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
21402ac6454SAndrew Thompson 		.direction = UE_DIR_OUT,
2154eae601eSAndrew Thompson 		.bufsize = BULK_SIZE,
2167cd72d85SHans Petter Selasky 		.flags = {.ext_buffer = 1,.proxy_buffer = 1,},
2174eae601eSAndrew Thompson 		.callback = &bbb_data_write_callback,
2184eae601eSAndrew Thompson 		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
21902ac6454SAndrew Thompson 	},
22002ac6454SAndrew Thompson 
22102ac6454SAndrew Thompson 	[ST_DATA_WR_CS] = {
22202ac6454SAndrew Thompson 		.type = UE_CONTROL,
22302ac6454SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
22402ac6454SAndrew Thompson 		.direction = UE_DIR_ANY,
225760bc48eSAndrew Thompson 		.bufsize = sizeof(struct usb_device_request),
2264eae601eSAndrew Thompson 		.callback = &bbb_data_wr_cs_callback,
2274eae601eSAndrew Thompson 		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
22802ac6454SAndrew Thompson 	},
22902ac6454SAndrew Thompson 
23002ac6454SAndrew Thompson 	[ST_STATUS] = {
23102ac6454SAndrew Thompson 		.type = UE_BULK,
23202ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
23302ac6454SAndrew Thompson 		.direction = UE_DIR_IN,
2344eae601eSAndrew Thompson 		.bufsize = sizeof(struct bbb_csw),
2357cd72d85SHans Petter Selasky 		.flags = {.ext_buffer = 1,.short_xfer_ok = 1,},
2364eae601eSAndrew Thompson 		.callback = &bbb_status_callback,
2374eae601eSAndrew Thompson 		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
23802ac6454SAndrew Thompson 	},
23902ac6454SAndrew Thompson };
24002ac6454SAndrew Thompson 
24102ac6454SAndrew Thompson static void
242d84a79e7SAndrew Thompson bbb_done(struct bbb_transfer *sc, int error)
24302ac6454SAndrew Thompson {
24402ac6454SAndrew Thompson 
24502ac6454SAndrew Thompson 	sc->error = error;
24602ac6454SAndrew Thompson 	sc->state = ST_COMMAND;
24702ac6454SAndrew Thompson 	sc->status_try = 1;
2488437751dSAndrew Thompson 	cv_signal(&sc->cv);
24902ac6454SAndrew Thompson }
25002ac6454SAndrew Thompson 
25102ac6454SAndrew Thompson static void
25202ac6454SAndrew Thompson bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index)
25302ac6454SAndrew Thompson {
25402ac6454SAndrew Thompson 	sc->state = xfer_index;
255a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->xfer[xfer_index]);
25602ac6454SAndrew Thompson }
25702ac6454SAndrew Thompson 
25802ac6454SAndrew Thompson static void
259760bc48eSAndrew Thompson bbb_data_clear_stall_callback(struct usb_xfer *xfer,
26002ac6454SAndrew Thompson     uint8_t next_xfer, uint8_t stall_xfer)
26102ac6454SAndrew Thompson {
262ed6d949aSAndrew Thompson 	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
26302ac6454SAndrew Thompson 
264a593f6b8SAndrew Thompson 	if (usbd_clear_stall_callback(xfer, sc->xfer[stall_xfer])) {
26502ac6454SAndrew Thompson 		switch (USB_GET_STATE(xfer)) {
26602ac6454SAndrew Thompson 		case USB_ST_SETUP:
26702ac6454SAndrew Thompson 		case USB_ST_TRANSFERRED:
26802ac6454SAndrew Thompson 			bbb_transfer_start(sc, next_xfer);
26902ac6454SAndrew Thompson 			break;
27002ac6454SAndrew Thompson 		default:
271d84a79e7SAndrew Thompson 			bbb_done(sc, USB_ERR_STALLED);
27202ac6454SAndrew Thompson 			break;
27302ac6454SAndrew Thompson 		}
27402ac6454SAndrew Thompson 	}
27502ac6454SAndrew Thompson }
27602ac6454SAndrew Thompson 
27702ac6454SAndrew Thompson static void
278ed6d949aSAndrew Thompson bbb_command_callback(struct usb_xfer *xfer, usb_error_t error)
27902ac6454SAndrew Thompson {
280ed6d949aSAndrew Thompson 	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
28102ac6454SAndrew Thompson 	uint32_t tag;
28202ac6454SAndrew Thompson 
28302ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
28402ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
28502ac6454SAndrew Thompson 		bbb_transfer_start
28602ac6454SAndrew Thompson 		    (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD :
28702ac6454SAndrew Thompson 		    (sc->dir == DIR_OUT) ? ST_DATA_WR :
28802ac6454SAndrew Thompson 		    ST_STATUS));
28902ac6454SAndrew Thompson 		break;
29002ac6454SAndrew Thompson 
29102ac6454SAndrew Thompson 	case USB_ST_SETUP:
29202ac6454SAndrew Thompson 		sc->status_try = 0;
29302ac6454SAndrew Thompson 		tag = UGETDW(sc->cbw.dCBWTag) + 1;
29402ac6454SAndrew Thompson 		USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE);
29502ac6454SAndrew Thompson 		USETDW(sc->cbw.dCBWTag, tag);
296578d0effSAndrew Thompson 		USETDW(sc->cbw.dCBWDataTransferLength, (uint32_t)sc->data_len);
29702ac6454SAndrew Thompson 		sc->cbw.bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT);
29802ac6454SAndrew Thompson 		sc->cbw.bCBWLUN = sc->lun;
29902ac6454SAndrew Thompson 		sc->cbw.bCDBLength = sc->cmd_len;
30002ac6454SAndrew Thompson 		if (sc->cbw.bCDBLength > sizeof(sc->cbw.CBWCDB)) {
30102ac6454SAndrew Thompson 			sc->cbw.bCDBLength = sizeof(sc->cbw.CBWCDB);
302767cb2e2SAndrew Thompson 			DPRINTFN(0, "Truncating long command\n");
30302ac6454SAndrew Thompson 		}
304ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_data(xfer, 0, &sc->cbw, sizeof(sc->cbw));
305a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
30602ac6454SAndrew Thompson 		break;
30702ac6454SAndrew Thompson 
30802ac6454SAndrew Thompson 	default:			/* Error */
309d84a79e7SAndrew Thompson 		bbb_done(sc, error);
31002ac6454SAndrew Thompson 		break;
31102ac6454SAndrew Thompson 	}
31202ac6454SAndrew Thompson }
31302ac6454SAndrew Thompson 
31402ac6454SAndrew Thompson static void
315ed6d949aSAndrew Thompson bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error)
31602ac6454SAndrew Thompson {
317ed6d949aSAndrew Thompson 	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
318ed6d949aSAndrew Thompson 	usb_frlength_t max_bulk = usbd_xfer_max_len(xfer);
319ed6d949aSAndrew Thompson 	int actlen, sumlen;
320ed6d949aSAndrew Thompson 
321ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
32202ac6454SAndrew Thompson 
32302ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
32402ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
325ed6d949aSAndrew Thompson 		sc->data_rem -= actlen;
326ed6d949aSAndrew Thompson 		sc->data_ptr += actlen;
327ed6d949aSAndrew Thompson 		sc->actlen += actlen;
32802ac6454SAndrew Thompson 
329ed6d949aSAndrew Thompson 		if (actlen < sumlen) {
33002ac6454SAndrew Thompson 			/* short transfer */
33102ac6454SAndrew Thompson 			sc->data_rem = 0;
33202ac6454SAndrew Thompson 		}
33302ac6454SAndrew Thompson 	case USB_ST_SETUP:
33402ac6454SAndrew Thompson 		DPRINTF("max_bulk=%d, data_rem=%d\n",
33502ac6454SAndrew Thompson 		    max_bulk, sc->data_rem);
33602ac6454SAndrew Thompson 
33702ac6454SAndrew Thompson 		if (sc->data_rem == 0) {
33802ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_STATUS);
33902ac6454SAndrew Thompson 			break;
34002ac6454SAndrew Thompson 		}
34102ac6454SAndrew Thompson 		if (max_bulk > sc->data_rem) {
34202ac6454SAndrew Thompson 			max_bulk = sc->data_rem;
34302ac6454SAndrew Thompson 		}
344ed6d949aSAndrew Thompson 		usbd_xfer_set_timeout(xfer, sc->data_timeout);
345ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk);
346a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
34702ac6454SAndrew Thompson 		break;
34802ac6454SAndrew Thompson 
34902ac6454SAndrew Thompson 	default:			/* Error */
350ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
351d84a79e7SAndrew Thompson 			bbb_done(sc, error);
35202ac6454SAndrew Thompson 		} else {
35302ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_RD_CS);
35402ac6454SAndrew Thompson 		}
35502ac6454SAndrew Thompson 		break;
35602ac6454SAndrew Thompson 	}
35702ac6454SAndrew Thompson }
35802ac6454SAndrew Thompson 
35902ac6454SAndrew Thompson static void
360ed6d949aSAndrew Thompson bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error)
36102ac6454SAndrew Thompson {
36202ac6454SAndrew Thompson 	bbb_data_clear_stall_callback(xfer, ST_STATUS,
36302ac6454SAndrew Thompson 	    ST_DATA_RD);
36402ac6454SAndrew Thompson }
36502ac6454SAndrew Thompson 
36602ac6454SAndrew Thompson static void
367ed6d949aSAndrew Thompson bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error)
36802ac6454SAndrew Thompson {
369ed6d949aSAndrew Thompson 	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
370ed6d949aSAndrew Thompson 	usb_frlength_t max_bulk = usbd_xfer_max_len(xfer);
371ed6d949aSAndrew Thompson 	int actlen, sumlen;
372ed6d949aSAndrew Thompson 
373ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
37402ac6454SAndrew Thompson 
37502ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
37602ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
377ed6d949aSAndrew Thompson 		sc->data_rem -= actlen;
378ed6d949aSAndrew Thompson 		sc->data_ptr += actlen;
379ed6d949aSAndrew Thompson 		sc->actlen += actlen;
38002ac6454SAndrew Thompson 
381ed6d949aSAndrew Thompson 		if (actlen < sumlen) {
38202ac6454SAndrew Thompson 			/* short transfer */
38302ac6454SAndrew Thompson 			sc->data_rem = 0;
38402ac6454SAndrew Thompson 		}
38502ac6454SAndrew Thompson 	case USB_ST_SETUP:
38602ac6454SAndrew Thompson 		DPRINTF("max_bulk=%d, data_rem=%d\n",
38702ac6454SAndrew Thompson 		    max_bulk, sc->data_rem);
38802ac6454SAndrew Thompson 
38902ac6454SAndrew Thompson 		if (sc->data_rem == 0) {
39002ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_STATUS);
39102ac6454SAndrew Thompson 			return;
39202ac6454SAndrew Thompson 		}
39302ac6454SAndrew Thompson 		if (max_bulk > sc->data_rem) {
39402ac6454SAndrew Thompson 			max_bulk = sc->data_rem;
39502ac6454SAndrew Thompson 		}
396ed6d949aSAndrew Thompson 		usbd_xfer_set_timeout(xfer, sc->data_timeout);
397ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk);
398a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
39902ac6454SAndrew Thompson 		return;
40002ac6454SAndrew Thompson 
40102ac6454SAndrew Thompson 	default:			/* Error */
402ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
403d84a79e7SAndrew Thompson 			bbb_done(sc, error);
40402ac6454SAndrew Thompson 		} else {
40502ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_WR_CS);
40602ac6454SAndrew Thompson 		}
40702ac6454SAndrew Thompson 		return;
40802ac6454SAndrew Thompson 
40902ac6454SAndrew Thompson 	}
41002ac6454SAndrew Thompson }
41102ac6454SAndrew Thompson 
41202ac6454SAndrew Thompson static void
413ed6d949aSAndrew Thompson bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error)
41402ac6454SAndrew Thompson {
41502ac6454SAndrew Thompson 	bbb_data_clear_stall_callback(xfer, ST_STATUS,
41602ac6454SAndrew Thompson 	    ST_DATA_WR);
41702ac6454SAndrew Thompson }
41802ac6454SAndrew Thompson 
41902ac6454SAndrew Thompson static void
420ed6d949aSAndrew Thompson bbb_status_callback(struct usb_xfer *xfer, usb_error_t error)
42102ac6454SAndrew Thompson {
422ed6d949aSAndrew Thompson 	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
4236d917491SHans Petter Selasky 	int actlen;
4246d917491SHans Petter Selasky 	int sumlen;
425ed6d949aSAndrew Thompson 
426ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
42702ac6454SAndrew Thompson 
42802ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
42902ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
43002ac6454SAndrew Thompson 
43102ac6454SAndrew Thompson 		/* very simple status check */
43202ac6454SAndrew Thompson 
4336d917491SHans Petter Selasky 		if (actlen < (int)sizeof(sc->csw)) {
434d84a79e7SAndrew Thompson 			bbb_done(sc, USB_ERR_SHORT_XFER);
43502ac6454SAndrew Thompson 		} else if (sc->csw.bCSWStatus == CSWSTATUS_GOOD) {
43602ac6454SAndrew Thompson 			bbb_done(sc, 0);	/* success */
43702ac6454SAndrew Thompson 		} else {
438d84a79e7SAndrew Thompson 			bbb_done(sc, ERR_CSW_FAILED);	/* error */
43902ac6454SAndrew Thompson 		}
44002ac6454SAndrew Thompson 		break;
44102ac6454SAndrew Thompson 
44202ac6454SAndrew Thompson 	case USB_ST_SETUP:
443ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_data(xfer, 0, &sc->csw, sizeof(sc->csw));
444a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
44502ac6454SAndrew Thompson 		break;
44602ac6454SAndrew Thompson 
44702ac6454SAndrew Thompson 	default:
448d84a79e7SAndrew Thompson 		DPRINTF("Failed to read CSW: %s, try %d\n",
449ed6d949aSAndrew Thompson 		    usbd_errstr(error), sc->status_try);
45002ac6454SAndrew Thompson 
451ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED || sc->status_try) {
452d84a79e7SAndrew Thompson 			bbb_done(sc, error);
45302ac6454SAndrew Thompson 		} else {
45402ac6454SAndrew Thompson 			sc->status_try = 1;
45502ac6454SAndrew Thompson 			bbb_transfer_start(sc, ST_DATA_RD_CS);
45602ac6454SAndrew Thompson 		}
45702ac6454SAndrew Thompson 		break;
45802ac6454SAndrew Thompson 	}
45902ac6454SAndrew Thompson }
46002ac6454SAndrew Thompson 
46102ac6454SAndrew Thompson /*------------------------------------------------------------------------*
46202ac6454SAndrew Thompson  *	bbb_command_start - execute a SCSI command synchronously
46302ac6454SAndrew Thompson  *
46402ac6454SAndrew Thompson  * Return values
46502ac6454SAndrew Thompson  * 0: Success
46602ac6454SAndrew Thompson  * Else: Failure
46702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
468d46dc4adSHans Petter Selasky static int
46902ac6454SAndrew Thompson bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun,
470d84a79e7SAndrew Thompson     void *data_ptr, size_t data_len, void *cmd_ptr, size_t cmd_len,
471e0a69b51SAndrew Thompson     usb_timeout_t data_timeout)
47202ac6454SAndrew Thompson {
47302ac6454SAndrew Thompson 	sc->lun = lun;
47402ac6454SAndrew Thompson 	sc->dir = data_len ? dir : DIR_NONE;
47502ac6454SAndrew Thompson 	sc->data_ptr = data_ptr;
47602ac6454SAndrew Thompson 	sc->data_len = data_len;
47702ac6454SAndrew Thompson 	sc->data_rem = data_len;
47802ac6454SAndrew Thompson 	sc->data_timeout = (data_timeout + USB_MS_HZ);
47902ac6454SAndrew Thompson 	sc->actlen = 0;
48002ac6454SAndrew Thompson 	sc->cmd_len = cmd_len;
481271ae033SHans Petter Selasky 	memset(&sc->cbw.CBWCDB, 0, sizeof(sc->cbw.CBWCDB));
482271ae033SHans Petter Selasky 	memcpy(&sc->cbw.CBWCDB, cmd_ptr, cmd_len);
48387812fceSHans Petter Selasky 	DPRINTFN(1, "SCSI cmd = %*D\n", (int)cmd_len, (char *)sc->cbw.CBWCDB, ":");
48402ac6454SAndrew Thompson 
485d84a79e7SAndrew Thompson 	mtx_lock(&sc->mtx);
486a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->xfer[sc->state]);
48702ac6454SAndrew Thompson 
488a593f6b8SAndrew Thompson 	while (usbd_transfer_pending(sc->xfer[sc->state])) {
4898437751dSAndrew Thompson 		cv_wait(&sc->cv, &sc->mtx);
49002ac6454SAndrew Thompson 	}
491d84a79e7SAndrew Thompson 	mtx_unlock(&sc->mtx);
49202ac6454SAndrew Thompson 	return (sc->error);
49302ac6454SAndrew Thompson }
49402ac6454SAndrew Thompson 
495d84a79e7SAndrew Thompson static struct bbb_transfer *
496d84a79e7SAndrew Thompson bbb_attach(struct usb_device *udev, uint8_t iface_index)
49702ac6454SAndrew Thompson {
498760bc48eSAndrew Thompson 	struct usb_interface *iface;
499760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
50002ac6454SAndrew Thompson 	struct bbb_transfer *sc;
501d84a79e7SAndrew Thompson 	usb_error_t err;
502a6b60150SHans Petter Selasky 	uint8_t do_unlock;
503a6b60150SHans Petter Selasky 
504a6b60150SHans Petter Selasky 	/* automatic locking */
505a6b60150SHans Petter Selasky 	if (usbd_enum_is_locked(udev)) {
506a6b60150SHans Petter Selasky 		do_unlock = 0;
507a6b60150SHans Petter Selasky 	} else {
508a6b60150SHans Petter Selasky 		do_unlock = 1;
509a6b60150SHans Petter Selasky 		usbd_enum_lock(udev);
510a6b60150SHans Petter Selasky 	}
511a6b60150SHans Petter Selasky 
512a6b60150SHans Petter Selasky 	/*
513a6b60150SHans Petter Selasky 	 * Make sure any driver which is hooked up to this interface,
514a6b60150SHans Petter Selasky 	 * like umass is gone:
515a6b60150SHans Petter Selasky 	 */
516a6b60150SHans Petter Selasky 	usb_detach_device(udev, iface_index, 0);
517a6b60150SHans Petter Selasky 
518a6b60150SHans Petter Selasky 	if (do_unlock)
519a6b60150SHans Petter Selasky 		usbd_enum_unlock(udev);
52002ac6454SAndrew Thompson 
521a593f6b8SAndrew Thompson 	iface = usbd_get_iface(udev, iface_index);
522d84a79e7SAndrew Thompson 	if (iface == NULL)
523d84a79e7SAndrew Thompson 		return (NULL);
524d84a79e7SAndrew Thompson 
52502ac6454SAndrew Thompson 	id = iface->idesc;
526d84a79e7SAndrew Thompson 	if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
527d84a79e7SAndrew Thompson 		return (NULL);
528d84a79e7SAndrew Thompson 
52902ac6454SAndrew Thompson 	switch (id->bInterfaceSubClass) {
53002ac6454SAndrew Thompson 	case UISUBCLASS_SCSI:
53102ac6454SAndrew Thompson 	case UISUBCLASS_UFI:
532d84a79e7SAndrew Thompson 	case UISUBCLASS_SFF8020I:
533d84a79e7SAndrew Thompson 	case UISUBCLASS_SFF8070I:
53402ac6454SAndrew Thompson 		break;
53502ac6454SAndrew Thompson 	default:
536d84a79e7SAndrew Thompson 		return (NULL);
53702ac6454SAndrew Thompson 	}
53802ac6454SAndrew Thompson 
53902ac6454SAndrew Thompson 	switch (id->bInterfaceProtocol) {
54002ac6454SAndrew Thompson 	case UIPROTO_MASS_BBB_OLD:
54102ac6454SAndrew Thompson 	case UIPROTO_MASS_BBB:
54202ac6454SAndrew Thompson 		break;
54302ac6454SAndrew Thompson 	default:
544d84a79e7SAndrew Thompson 		return (NULL);
54502ac6454SAndrew Thompson 	}
54602ac6454SAndrew Thompson 
54702ac6454SAndrew Thompson 	sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO);
54802ac6454SAndrew Thompson 	mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF);
5498437751dSAndrew Thompson 	cv_init(&sc->cv, "WBBB");
55002ac6454SAndrew Thompson 
551d84a79e7SAndrew Thompson 	err = usbd_transfer_setup(udev, &iface_index, sc->xfer, bbb_config,
55202ac6454SAndrew Thompson 	    ST_MAX, sc, &sc->mtx);
55302ac6454SAndrew Thompson 	if (err) {
554d84a79e7SAndrew Thompson 		bbb_detach(sc);
555d84a79e7SAndrew Thompson 		return (NULL);
55602ac6454SAndrew Thompson 	}
557d84a79e7SAndrew Thompson 	return (sc);
55802ac6454SAndrew Thompson }
55902ac6454SAndrew Thompson 
560d84a79e7SAndrew Thompson static void
561d84a79e7SAndrew Thompson bbb_detach(struct bbb_transfer *sc)
562d84a79e7SAndrew Thompson {
563a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(sc->xfer, ST_MAX);
56402ac6454SAndrew Thompson 	mtx_destroy(&sc->mtx);
5658437751dSAndrew Thompson 	cv_destroy(&sc->cv);
56602ac6454SAndrew Thompson 	free(sc, M_USB);
567d84a79e7SAndrew Thompson }
568d84a79e7SAndrew Thompson 
569d84a79e7SAndrew Thompson /*------------------------------------------------------------------------*
570d84a79e7SAndrew Thompson  *	usb_iface_is_cdrom
571d84a79e7SAndrew Thompson  *
572d84a79e7SAndrew Thompson  * Return values:
573d84a79e7SAndrew Thompson  * 1: This interface is an auto install disk (CD-ROM)
574d84a79e7SAndrew Thompson  * 0: Not an auto install disk.
575d84a79e7SAndrew Thompson  *------------------------------------------------------------------------*/
576d84a79e7SAndrew Thompson int
577d84a79e7SAndrew Thompson usb_iface_is_cdrom(struct usb_device *udev, uint8_t iface_index)
578d84a79e7SAndrew Thompson {
579d84a79e7SAndrew Thompson 	struct bbb_transfer *sc;
580d46dc4adSHans Petter Selasky 	uint8_t timeout;
581d46dc4adSHans Petter Selasky 	uint8_t is_cdrom;
582d84a79e7SAndrew Thompson 	uint8_t sid_type;
583d46dc4adSHans Petter Selasky 	int err;
584d84a79e7SAndrew Thompson 
585d84a79e7SAndrew Thompson 	sc = bbb_attach(udev, iface_index);
586d84a79e7SAndrew Thompson 	if (sc == NULL)
587d84a79e7SAndrew Thompson 		return (0);
588d84a79e7SAndrew Thompson 
589d84a79e7SAndrew Thompson 	is_cdrom = 0;
590d84a79e7SAndrew Thompson 	timeout = 4;	/* tries */
591d84a79e7SAndrew Thompson 	while (--timeout) {
592d84a79e7SAndrew Thompson 		err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
593d84a79e7SAndrew Thompson 		    SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
594d84a79e7SAndrew Thompson 		    USB_MS_HZ);
595d84a79e7SAndrew Thompson 
596d84a79e7SAndrew Thompson 		if (err == 0 && sc->actlen > 0) {
597d84a79e7SAndrew Thompson 			sid_type = sc->buffer[0] & 0x1F;
598d84a79e7SAndrew Thompson 			if (sid_type == 0x05)
599d84a79e7SAndrew Thompson 				is_cdrom = 1;
600d84a79e7SAndrew Thompson 			break;
601d84a79e7SAndrew Thompson 		} else if (err != ERR_CSW_FAILED)
602d84a79e7SAndrew Thompson 			break;	/* non retryable error */
603d84a79e7SAndrew Thompson 		usb_pause_mtx(NULL, hz);
604d84a79e7SAndrew Thompson 	}
605d84a79e7SAndrew Thompson 	bbb_detach(sc);
606d84a79e7SAndrew Thompson 	return (is_cdrom);
607d84a79e7SAndrew Thompson }
608d84a79e7SAndrew Thompson 
6099157ad4bSHans Petter Selasky static uint8_t
6109157ad4bSHans Petter Selasky usb_msc_get_max_lun(struct usb_device *udev, uint8_t iface_index)
6119157ad4bSHans Petter Selasky {
6129157ad4bSHans Petter Selasky 	struct usb_device_request req;
6139157ad4bSHans Petter Selasky 	usb_error_t err;
6149157ad4bSHans Petter Selasky 	uint8_t buf = 0;
6159157ad4bSHans Petter Selasky 
6169157ad4bSHans Petter Selasky 
6179157ad4bSHans Petter Selasky 	/* The Get Max Lun command is a class-specific request. */
6189157ad4bSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
6199157ad4bSHans Petter Selasky 	req.bRequest = 0xFE;		/* GET_MAX_LUN */
6209157ad4bSHans Petter Selasky 	USETW(req.wValue, 0);
6219157ad4bSHans Petter Selasky 	req.wIndex[0] = iface_index;
6229157ad4bSHans Petter Selasky 	req.wIndex[1] = 0;
6239157ad4bSHans Petter Selasky 	USETW(req.wLength, 1);
6249157ad4bSHans Petter Selasky 
6259157ad4bSHans Petter Selasky 	err = usbd_do_request(udev, NULL, &req, &buf);
6269157ad4bSHans Petter Selasky 	if (err)
6279157ad4bSHans Petter Selasky 		buf = 0;
6289157ad4bSHans Petter Selasky 
6299157ad4bSHans Petter Selasky 	return (buf);
6309157ad4bSHans Petter Selasky }
6319157ad4bSHans Petter Selasky 
632d84a79e7SAndrew Thompson usb_error_t
633d46dc4adSHans Petter Selasky usb_msc_auto_quirk(struct usb_device *udev, uint8_t iface_index)
634d46dc4adSHans Petter Selasky {
635d46dc4adSHans Petter Selasky 	struct bbb_transfer *sc;
636d46dc4adSHans Petter Selasky 	uint8_t timeout;
637d46dc4adSHans Petter Selasky 	uint8_t is_no_direct;
638d46dc4adSHans Petter Selasky 	uint8_t sid_type;
639d46dc4adSHans Petter Selasky 	int err;
640d46dc4adSHans Petter Selasky 
641d46dc4adSHans Petter Selasky 	sc = bbb_attach(udev, iface_index);
642d46dc4adSHans Petter Selasky 	if (sc == NULL)
643d46dc4adSHans Petter Selasky 		return (0);
644d46dc4adSHans Petter Selasky 
645d46dc4adSHans Petter Selasky 	/*
646d46dc4adSHans Petter Selasky 	 * Some devices need a delay after that the configuration
647d46dc4adSHans Petter Selasky 	 * value is set to function properly:
648d46dc4adSHans Petter Selasky 	 */
649d46dc4adSHans Petter Selasky 	usb_pause_mtx(NULL, hz);
650d46dc4adSHans Petter Selasky 
6519157ad4bSHans Petter Selasky 	if (usb_msc_get_max_lun(udev, iface_index) == 0) {
6529157ad4bSHans Petter Selasky 		DPRINTF("Device has only got one LUN.\n");
6539157ad4bSHans Petter Selasky 		usbd_add_dynamic_quirk(udev, UQ_MSC_NO_GETMAXLUN);
6549157ad4bSHans Petter Selasky 	}
6559157ad4bSHans Petter Selasky 
656d46dc4adSHans Petter Selasky 	is_no_direct = 1;
657*0cb5abc7SHans Petter Selasky 	for (timeout = 4; timeout != 0; timeout--) {
658d46dc4adSHans Petter Selasky 		err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
659d46dc4adSHans Petter Selasky 		    SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
660d46dc4adSHans Petter Selasky 		    USB_MS_HZ);
661d46dc4adSHans Petter Selasky 
662d46dc4adSHans Petter Selasky 		if (err == 0 && sc->actlen > 0) {
663d46dc4adSHans Petter Selasky 			sid_type = sc->buffer[0] & 0x1F;
664d46dc4adSHans Petter Selasky 			if (sid_type == 0x00)
665d46dc4adSHans Petter Selasky 				is_no_direct = 0;
666d46dc4adSHans Petter Selasky 			break;
667d46dc4adSHans Petter Selasky 		} else if (err != ERR_CSW_FAILED)
668d46dc4adSHans Petter Selasky 			break;	/* non retryable error */
669d46dc4adSHans Petter Selasky 		usb_pause_mtx(NULL, hz);
670d46dc4adSHans Petter Selasky 	}
671d46dc4adSHans Petter Selasky 
672d46dc4adSHans Petter Selasky 	if (is_no_direct) {
673d46dc4adSHans Petter Selasky 		DPRINTF("Device is not direct access.\n");
674d46dc4adSHans Petter Selasky 		goto done;
675d46dc4adSHans Petter Selasky 	}
676d46dc4adSHans Petter Selasky 
677d46dc4adSHans Petter Selasky 	err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
678d46dc4adSHans Petter Selasky 	    &scsi_test_unit_ready, sizeof(scsi_test_unit_ready),
679d46dc4adSHans Petter Selasky 	    USB_MS_HZ);
680d46dc4adSHans Petter Selasky 
681d46dc4adSHans Petter Selasky 	if (err != 0) {
682d46dc4adSHans Petter Selasky 
683d46dc4adSHans Petter Selasky 		if (err != ERR_CSW_FAILED)
684d46dc4adSHans Petter Selasky 			goto error;
685d46dc4adSHans Petter Selasky 	}
686*0cb5abc7SHans Petter Selasky 	timeout = 1;
687d46dc4adSHans Petter Selasky 
688*0cb5abc7SHans Petter Selasky retry_sync_cache:
689d46dc4adSHans Petter Selasky 	err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
690d46dc4adSHans Petter Selasky 	    &scsi_sync_cache, sizeof(scsi_sync_cache),
691d46dc4adSHans Petter Selasky 	    USB_MS_HZ);
692d46dc4adSHans Petter Selasky 
693d46dc4adSHans Petter Selasky 	if (err != 0) {
694d46dc4adSHans Petter Selasky 
695d46dc4adSHans Petter Selasky 		if (err != ERR_CSW_FAILED)
696d46dc4adSHans Petter Selasky 			goto error;
697d46dc4adSHans Petter Selasky 
698d46dc4adSHans Petter Selasky 		DPRINTF("Device doesn't handle synchronize cache\n");
699d46dc4adSHans Petter Selasky 
700d46dc4adSHans Petter Selasky 		usbd_add_dynamic_quirk(udev, UQ_MSC_NO_SYNC_CACHE);
701*0cb5abc7SHans Petter Selasky 
702*0cb5abc7SHans Petter Selasky 	} else {
703*0cb5abc7SHans Petter Selasky 
704*0cb5abc7SHans Petter Selasky 		/*
705*0cb5abc7SHans Petter Selasky 		 * Certain Kingston memory sticks fail the first
706*0cb5abc7SHans Petter Selasky 		 * read capacity after a synchronize cache command
707*0cb5abc7SHans Petter Selasky 		 * has been issued. Disable the synchronize cache
708*0cb5abc7SHans Petter Selasky 		 * command for such devices.
709*0cb5abc7SHans Petter Selasky 		 */
710*0cb5abc7SHans Petter Selasky 
711*0cb5abc7SHans Petter Selasky 		err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8,
712*0cb5abc7SHans Petter Selasky 		    &scsi_read_capacity, sizeof(scsi_read_capacity),
713*0cb5abc7SHans Petter Selasky 		    USB_MS_HZ);
714*0cb5abc7SHans Petter Selasky 
715*0cb5abc7SHans Petter Selasky 		if (err != 0) {
716*0cb5abc7SHans Petter Selasky 			if (err != ERR_CSW_FAILED)
717*0cb5abc7SHans Petter Selasky 				goto error;
718*0cb5abc7SHans Petter Selasky 
719*0cb5abc7SHans Petter Selasky 			err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8,
720*0cb5abc7SHans Petter Selasky 			    &scsi_read_capacity, sizeof(scsi_read_capacity),
721*0cb5abc7SHans Petter Selasky 			    USB_MS_HZ);
722*0cb5abc7SHans Petter Selasky 
723*0cb5abc7SHans Petter Selasky 			if (err == 0) {
724*0cb5abc7SHans Petter Selasky 				if (timeout--)
725*0cb5abc7SHans Petter Selasky 					goto retry_sync_cache;
726*0cb5abc7SHans Petter Selasky 
727*0cb5abc7SHans Petter Selasky 				DPRINTF("Device most likely doesn't "
728*0cb5abc7SHans Petter Selasky 				    "handle synchronize cache\n");
729*0cb5abc7SHans Petter Selasky 
730*0cb5abc7SHans Petter Selasky 				usbd_add_dynamic_quirk(udev,
731*0cb5abc7SHans Petter Selasky 				    UQ_MSC_NO_SYNC_CACHE);
732*0cb5abc7SHans Petter Selasky 			} else {
733*0cb5abc7SHans Petter Selasky 				if (err != ERR_CSW_FAILED)
734*0cb5abc7SHans Petter Selasky 					goto error;
735*0cb5abc7SHans Petter Selasky 			}
736*0cb5abc7SHans Petter Selasky 		}
737d46dc4adSHans Petter Selasky 	}
738d46dc4adSHans Petter Selasky 
739d334432fSHans Petter Selasky 	/* clear sense status of any failed commands on the device */
740d334432fSHans Petter Selasky 
741d334432fSHans Petter Selasky 	err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
742d334432fSHans Petter Selasky 	    SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
743d334432fSHans Petter Selasky 	    USB_MS_HZ);
744d334432fSHans Petter Selasky 
745d334432fSHans Petter Selasky 	DPRINTF("Inquiry = %d\n", err);
746d334432fSHans Petter Selasky 
747d334432fSHans Petter Selasky 	if (err != 0) {
748d334432fSHans Petter Selasky 
749d334432fSHans Petter Selasky 		if (err != ERR_CSW_FAILED)
750d334432fSHans Petter Selasky 			goto error;
751d334432fSHans Petter Selasky 	}
752d334432fSHans Petter Selasky 
753d334432fSHans Petter Selasky 	err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
754d334432fSHans Petter Selasky 	    SCSI_SENSE_LEN, &scsi_request_sense,
755d334432fSHans Petter Selasky 	    sizeof(scsi_request_sense), USB_MS_HZ);
756d334432fSHans Petter Selasky 
757d334432fSHans Petter Selasky 	DPRINTF("Request sense = %d\n", err);
758d334432fSHans Petter Selasky 
759d334432fSHans Petter Selasky 	if (err != 0) {
760d334432fSHans Petter Selasky 
761d334432fSHans Petter Selasky 		if (err != ERR_CSW_FAILED)
762d334432fSHans Petter Selasky 			goto error;
763d334432fSHans Petter Selasky 	}
764d334432fSHans Petter Selasky 
765d46dc4adSHans Petter Selasky done:
766d46dc4adSHans Petter Selasky 	bbb_detach(sc);
767d46dc4adSHans Petter Selasky 	return (0);
768d46dc4adSHans Petter Selasky 
769d46dc4adSHans Petter Selasky error:
770d46dc4adSHans Petter Selasky  	bbb_detach(sc);
771d46dc4adSHans Petter Selasky 
772d46dc4adSHans Petter Selasky 	DPRINTF("Device did not respond, enabling all quirks\n");
773d46dc4adSHans Petter Selasky 
774d46dc4adSHans Petter Selasky 	usbd_add_dynamic_quirk(udev, UQ_MSC_NO_SYNC_CACHE);
775d46dc4adSHans Petter Selasky 	usbd_add_dynamic_quirk(udev, UQ_MSC_NO_TEST_UNIT_READY);
776d46dc4adSHans Petter Selasky 
777d46dc4adSHans Petter Selasky 	/* Need to re-enumerate the device */
778d46dc4adSHans Petter Selasky 	usbd_req_re_enumerate(udev, NULL);
779d46dc4adSHans Petter Selasky 
780d46dc4adSHans Petter Selasky 	return (USB_ERR_STALLED);
781d46dc4adSHans Petter Selasky }
782d46dc4adSHans Petter Selasky 
783d46dc4adSHans Petter Selasky usb_error_t
784d84a79e7SAndrew Thompson usb_msc_eject(struct usb_device *udev, uint8_t iface_index, int method)
785d84a79e7SAndrew Thompson {
786d84a79e7SAndrew Thompson 	struct bbb_transfer *sc;
787d84a79e7SAndrew Thompson 	usb_error_t err;
788d84a79e7SAndrew Thompson 
789d84a79e7SAndrew Thompson 	sc = bbb_attach(udev, iface_index);
790d84a79e7SAndrew Thompson 	if (sc == NULL)
791d84a79e7SAndrew Thompson 		return (USB_ERR_INVAL);
792d84a79e7SAndrew Thompson 
793d84a79e7SAndrew Thompson 	err = 0;
794d84a79e7SAndrew Thompson 	switch (method) {
795d84a79e7SAndrew Thompson 	case MSC_EJECT_STOPUNIT:
796d84a79e7SAndrew Thompson 		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
797d84a79e7SAndrew Thompson 		    &scsi_test_unit_ready, sizeof(scsi_test_unit_ready),
798d84a79e7SAndrew Thompson 		    USB_MS_HZ);
799d84a79e7SAndrew Thompson 		DPRINTF("Test unit ready status: %s\n", usbd_errstr(err));
800d84a79e7SAndrew Thompson 		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
801d84a79e7SAndrew Thompson 		    &scsi_start_stop_unit, sizeof(scsi_start_stop_unit),
802d84a79e7SAndrew Thompson 		    USB_MS_HZ);
803d84a79e7SAndrew Thompson 		break;
804d84a79e7SAndrew Thompson 	case MSC_EJECT_REZERO:
805d84a79e7SAndrew Thompson 		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
806d84a79e7SAndrew Thompson 		    &scsi_rezero_init, sizeof(scsi_rezero_init),
807d84a79e7SAndrew Thompson 		    USB_MS_HZ);
808d84a79e7SAndrew Thompson 		break;
809d84a79e7SAndrew Thompson 	case MSC_EJECT_ZTESTOR:
810d84a79e7SAndrew Thompson 		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
811d84a79e7SAndrew Thompson 		    &scsi_ztestor_eject, sizeof(scsi_ztestor_eject),
812d84a79e7SAndrew Thompson 		    USB_MS_HZ);
813d84a79e7SAndrew Thompson 		break;
814d84a79e7SAndrew Thompson 	case MSC_EJECT_CMOTECH:
815d84a79e7SAndrew Thompson 		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
816d84a79e7SAndrew Thompson 		    &scsi_cmotech_eject, sizeof(scsi_cmotech_eject),
817d84a79e7SAndrew Thompson 		    USB_MS_HZ);
818d84a79e7SAndrew Thompson 		break;
8192386d714SAndrew Thompson 	case MSC_EJECT_HUAWEI:
8202386d714SAndrew Thompson 		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
8212386d714SAndrew Thompson 		    &scsi_huawei_eject, sizeof(scsi_huawei_eject),
8222386d714SAndrew Thompson 		    USB_MS_HZ);
8232386d714SAndrew Thompson 		break;
824fb7a4d49SGleb Smirnoff 	case MSC_EJECT_TCT:
82519f54349SGleb Smirnoff 		/*
82619f54349SGleb Smirnoff 		 * TCTMobile needs DIR_IN flag. To get it, we
82719f54349SGleb Smirnoff 		 * supply a dummy data with the command.
82819f54349SGleb Smirnoff 		 */
82919f54349SGleb Smirnoff 		err = bbb_command_start(sc, DIR_IN, 0, &sc->buffer,
83019f54349SGleb Smirnoff 		    sizeof(sc->buffer), &scsi_tct_eject,
831fb7a4d49SGleb Smirnoff 		    sizeof(scsi_tct_eject), USB_MS_HZ);
832fb7a4d49SGleb Smirnoff 		break;
833d84a79e7SAndrew Thompson 	default:
834d84a79e7SAndrew Thompson 		printf("usb_msc_eject: unknown eject method (%d)\n", method);
835d84a79e7SAndrew Thompson 		break;
836d84a79e7SAndrew Thompson 	}
837d84a79e7SAndrew Thompson 	DPRINTF("Eject CD command status: %s\n", usbd_errstr(err));
838d84a79e7SAndrew Thompson 
839d84a79e7SAndrew Thompson 	bbb_detach(sc);
840d84a79e7SAndrew Thompson 	return (0);
84102ac6454SAndrew Thompson }
842