1184610Salfred/* $FreeBSD$ */
2184610Salfred/*-
3225400Shselasky * Copyright (c) 2008,2011 Hans Petter Selasky. All rights reserved.
4184610Salfred *
5184610Salfred * Redistribution and use in source and binary forms, with or without
6184610Salfred * modification, are permitted provided that the following conditions
7184610Salfred * are met:
8184610Salfred * 1. Redistributions of source code must retain the above copyright
9184610Salfred *    notice, this list of conditions and the following disclaimer.
10184610Salfred * 2. Redistributions in binary form must reproduce the above copyright
11184610Salfred *    notice, this list of conditions and the following disclaimer in the
12184610Salfred *    documentation and/or other materials provided with the distribution.
13184610Salfred *
14184610Salfred * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15184610Salfred * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16184610Salfred * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17184610Salfred * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18184610Salfred * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19184610Salfred * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20184610Salfred * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21184610Salfred * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22184610Salfred * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23184610Salfred * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24184610Salfred * SUCH DAMAGE.
25184610Salfred */
26184610Salfred
27184610Salfred/*
28184610Salfred * The following file contains code that will detect USB autoinstall
29184610Salfred * disks.
30184610Salfred *
31184610Salfred * TODO: Potentially we could add code to automatically detect USB
32184610Salfred * mass storage quirks for not supported SCSI commands!
33184610Salfred */
34184610Salfred
35194677Sthompsa#include <sys/stdint.h>
36194677Sthompsa#include <sys/stddef.h>
37194677Sthompsa#include <sys/param.h>
38194677Sthompsa#include <sys/queue.h>
39194677Sthompsa#include <sys/types.h>
40194677Sthompsa#include <sys/systm.h>
41194677Sthompsa#include <sys/kernel.h>
42194677Sthompsa#include <sys/bus.h>
43194677Sthompsa#include <sys/module.h>
44194677Sthompsa#include <sys/lock.h>
45194677Sthompsa#include <sys/mutex.h>
46194677Sthompsa#include <sys/condvar.h>
47194677Sthompsa#include <sys/sysctl.h>
48194677Sthompsa#include <sys/sx.h>
49194677Sthompsa#include <sys/unistd.h>
50194677Sthompsa#include <sys/callout.h>
51194677Sthompsa#include <sys/malloc.h>
52194677Sthompsa#include <sys/priv.h>
53194677Sthompsa
54188942Sthompsa#include <dev/usb/usb.h>
55194677Sthompsa#include <dev/usb/usbdi.h>
56194677Sthompsa#include <dev/usb/usbdi_util.h>
57184610Salfred
58194228Sthompsa#define	USB_DEBUG_VAR usb_debug
59184610Salfred
60188942Sthompsa#include <dev/usb/usb_busdma.h>
61188942Sthompsa#include <dev/usb/usb_process.h>
62188942Sthompsa#include <dev/usb/usb_transfer.h>
63188942Sthompsa#include <dev/usb/usb_msctest.h>
64188942Sthompsa#include <dev/usb/usb_debug.h>
65188942Sthompsa#include <dev/usb/usb_device.h>
66188942Sthompsa#include <dev/usb/usb_request.h>
67188942Sthompsa#include <dev/usb/usb_util.h>
68201681Sthompsa#include <dev/usb/quirk/usb_quirk.h>
69184610Salfred
70184610Salfredenum {
71184610Salfred	ST_COMMAND,
72184610Salfred	ST_DATA_RD,
73184610Salfred	ST_DATA_RD_CS,
74184610Salfred	ST_DATA_WR,
75184610Salfred	ST_DATA_WR_CS,
76184610Salfred	ST_STATUS,
77184610Salfred	ST_MAX,
78184610Salfred};
79184610Salfred
80184610Salfredenum {
81184610Salfred	DIR_IN,
82184610Salfred	DIR_OUT,
83184610Salfred	DIR_NONE,
84184610Salfred};
85184610Salfred
86260575Shselasky#define	SCSI_MAX_LEN	MAX(0x100, BULK_SIZE)
87201681Sthompsa#define	SCSI_INQ_LEN	0x24
88225400Shselasky#define	SCSI_SENSE_LEN	0xFF
89225400Shselasky
90201681Sthompsastatic uint8_t scsi_test_unit_ready[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
91201681Sthompsastatic uint8_t scsi_inquiry[] = { 0x12, 0x00, 0x00, 0x00, SCSI_INQ_LEN, 0x00 };
92201681Sthompsastatic uint8_t scsi_rezero_init[] =     { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
93201681Sthompsastatic uint8_t scsi_start_stop_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00 };
94201681Sthompsastatic uint8_t scsi_ztestor_eject[] =   { 0x85, 0x01, 0x01, 0x01, 0x18, 0x01,
95201681Sthompsa					  0x01, 0x01, 0x01, 0x01, 0x00, 0x00 };
96201681Sthompsastatic uint8_t scsi_cmotech_eject[] =   { 0xff, 0x52, 0x44, 0x45, 0x56, 0x43,
97201681Sthompsa					  0x48, 0x47 };
98203905Sthompsastatic uint8_t scsi_huawei_eject[] =	{ 0x11, 0x06, 0x00, 0x00, 0x00, 0x00,
99203905Sthompsa					  0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
100203905Sthompsa					  0x00, 0x00, 0x00, 0x00 };
101213480Sglebiusstatic uint8_t scsi_tct_eject[] =	{ 0x06, 0xf5, 0x04, 0x02, 0x52, 0x70 };
102225350Shselaskystatic uint8_t scsi_sync_cache[] =	{ 0x35, 0x00, 0x00, 0x00, 0x00, 0x00,
103225350Shselasky					  0x00, 0x00, 0x00, 0x00 };
104225400Shselaskystatic uint8_t scsi_request_sense[] =	{ 0x03, 0x00, 0x00, 0x00, 0x12, 0x00,
105225400Shselasky					  0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
106240666Shselaskystatic uint8_t scsi_read_capacity[] =	{ 0x25, 0x00, 0x00, 0x00, 0x00, 0x00,
107240666Shselasky					  0x00, 0x00, 0x00, 0x00 };
108201681Sthompsa
109185290Salfred#define	BULK_SIZE		64	/* dummy */
110201681Sthompsa#define	ERR_CSW_FAILED		-1
111184610Salfred
112184610Salfred/* Command Block Wrapper */
113184610Salfredstruct bbb_cbw {
114184610Salfred	uDWord	dCBWSignature;
115184610Salfred#define	CBWSIGNATURE	0x43425355
116184610Salfred	uDWord	dCBWTag;
117184610Salfred	uDWord	dCBWDataTransferLength;
118184610Salfred	uByte	bCBWFlags;
119184610Salfred#define	CBWFLAGS_OUT	0x00
120184610Salfred#define	CBWFLAGS_IN	0x80
121184610Salfred	uByte	bCBWLUN;
122184610Salfred	uByte	bCDBLength;
123184610Salfred#define	CBWCDBLENGTH	16
124184610Salfred	uByte	CBWCDB[CBWCDBLENGTH];
125184610Salfred} __packed;
126184610Salfred
127184610Salfred/* Command Status Wrapper */
128184610Salfredstruct bbb_csw {
129184610Salfred	uDWord	dCSWSignature;
130184610Salfred#define	CSWSIGNATURE	0x53425355
131184610Salfred	uDWord	dCSWTag;
132184610Salfred	uDWord	dCSWDataResidue;
133184610Salfred	uByte	bCSWStatus;
134184610Salfred#define	CSWSTATUS_GOOD	0x0
135184610Salfred#define	CSWSTATUS_FAILED	0x1
136184610Salfred#define	CSWSTATUS_PHASE	0x2
137184610Salfred} __packed;
138184610Salfred
139184610Salfredstruct bbb_transfer {
140184610Salfred	struct mtx mtx;
141184610Salfred	struct cv cv;
142259454Shselasky	struct bbb_cbw *cbw;
143259454Shselasky	struct bbb_csw *csw;
144184610Salfred
145192984Sthompsa	struct usb_xfer *xfer[ST_MAX];
146184610Salfred
147184610Salfred	uint8_t *data_ptr;
148184610Salfred
149193074Sthompsa	usb_size_t data_len;		/* bytes */
150193074Sthompsa	usb_size_t data_rem;		/* bytes */
151193045Sthompsa	usb_timeout_t data_timeout;	/* ms */
152193045Sthompsa	usb_frlength_t actlen;		/* bytes */
153260575Shselasky	usb_frlength_t buffer_size;    	/* bytes */
154184610Salfred
155184610Salfred	uint8_t	cmd_len;		/* bytes */
156184610Salfred	uint8_t	dir;
157184610Salfred	uint8_t	lun;
158184610Salfred	uint8_t	state;
159184610Salfred	uint8_t	status_try;
160201681Sthompsa	int	error;
161184610Salfred
162259454Shselasky	uint8_t	*buffer;
163184610Salfred};
164184610Salfred
165193045Sthompsastatic usb_callback_t bbb_command_callback;
166193045Sthompsastatic usb_callback_t bbb_data_read_callback;
167193045Sthompsastatic usb_callback_t bbb_data_rd_cs_callback;
168193045Sthompsastatic usb_callback_t bbb_data_write_callback;
169193045Sthompsastatic usb_callback_t bbb_data_wr_cs_callback;
170193045Sthompsastatic usb_callback_t bbb_status_callback;
171184610Salfred
172201681Sthompsastatic void	bbb_done(struct bbb_transfer *, int);
173201681Sthompsastatic void	bbb_transfer_start(struct bbb_transfer *, uint8_t);
174201681Sthompsastatic void	bbb_data_clear_stall_callback(struct usb_xfer *, uint8_t,
175201681Sthompsa		    uint8_t);
176225350Shselaskystatic int	bbb_command_start(struct bbb_transfer *, uint8_t, uint8_t,
177201681Sthompsa		    void *, size_t, void *, size_t, usb_timeout_t);
178201681Sthompsastatic struct bbb_transfer *bbb_attach(struct usb_device *, uint8_t);
179201681Sthompsastatic void	bbb_detach(struct bbb_transfer *);
180201681Sthompsa
181192984Sthompsastatic const struct usb_config bbb_config[ST_MAX] = {
182184610Salfred
183184610Salfred	[ST_COMMAND] = {
184184610Salfred		.type = UE_BULK,
185184610Salfred		.endpoint = UE_ADDR_ANY,
186184610Salfred		.direction = UE_DIR_OUT,
187190734Sthompsa		.bufsize = sizeof(struct bbb_cbw),
188190734Sthompsa		.callback = &bbb_command_callback,
189190734Sthompsa		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
190184610Salfred	},
191184610Salfred
192184610Salfred	[ST_DATA_RD] = {
193184610Salfred		.type = UE_BULK,
194184610Salfred		.endpoint = UE_ADDR_ANY,
195184610Salfred		.direction = UE_DIR_IN,
196260575Shselasky		.bufsize = SCSI_MAX_LEN,
197259454Shselasky		.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,},
198190734Sthompsa		.callback = &bbb_data_read_callback,
199190734Sthompsa		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
200184610Salfred	},
201184610Salfred
202184610Salfred	[ST_DATA_RD_CS] = {
203184610Salfred		.type = UE_CONTROL,
204184610Salfred		.endpoint = 0x00,	/* Control pipe */
205184610Salfred		.direction = UE_DIR_ANY,
206192984Sthompsa		.bufsize = sizeof(struct usb_device_request),
207190734Sthompsa		.callback = &bbb_data_rd_cs_callback,
208190734Sthompsa		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
209184610Salfred	},
210184610Salfred
211184610Salfred	[ST_DATA_WR] = {
212184610Salfred		.type = UE_BULK,
213184610Salfred		.endpoint = UE_ADDR_ANY,
214184610Salfred		.direction = UE_DIR_OUT,
215260575Shselasky		.bufsize = SCSI_MAX_LEN,
216225556Shselasky		.flags = {.ext_buffer = 1,.proxy_buffer = 1,},
217190734Sthompsa		.callback = &bbb_data_write_callback,
218190734Sthompsa		.timeout = 4 * USB_MS_HZ,	/* 4 seconds */
219184610Salfred	},
220184610Salfred
221184610Salfred	[ST_DATA_WR_CS] = {
222184610Salfred		.type = UE_CONTROL,
223184610Salfred		.endpoint = 0x00,	/* Control pipe */
224184610Salfred		.direction = UE_DIR_ANY,
225192984Sthompsa		.bufsize = sizeof(struct usb_device_request),
226190734Sthompsa		.callback = &bbb_data_wr_cs_callback,
227190734Sthompsa		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
228184610Salfred	},
229184610Salfred
230184610Salfred	[ST_STATUS] = {
231184610Salfred		.type = UE_BULK,
232184610Salfred		.endpoint = UE_ADDR_ANY,
233184610Salfred		.direction = UE_DIR_IN,
234190734Sthompsa		.bufsize = sizeof(struct bbb_csw),
235259454Shselasky		.flags = {.short_xfer_ok = 1,},
236190734Sthompsa		.callback = &bbb_status_callback,
237190734Sthompsa		.timeout = 1 * USB_MS_HZ,	/* 1 second  */
238184610Salfred	},
239184610Salfred};
240184610Salfred
241184610Salfredstatic void
242201681Sthompsabbb_done(struct bbb_transfer *sc, int error)
243184610Salfred{
244184610Salfred	sc->error = error;
245184610Salfred	sc->state = ST_COMMAND;
246184610Salfred	sc->status_try = 1;
247194227Sthompsa	cv_signal(&sc->cv);
248184610Salfred}
249184610Salfred
250184610Salfredstatic void
251184610Salfredbbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index)
252184610Salfred{
253184610Salfred	sc->state = xfer_index;
254194228Sthompsa	usbd_transfer_start(sc->xfer[xfer_index]);
255184610Salfred}
256184610Salfred
257184610Salfredstatic void
258192984Sthompsabbb_data_clear_stall_callback(struct usb_xfer *xfer,
259184610Salfred    uint8_t next_xfer, uint8_t stall_xfer)
260184610Salfred{
261194677Sthompsa	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
262184610Salfred
263194228Sthompsa	if (usbd_clear_stall_callback(xfer, sc->xfer[stall_xfer])) {
264184610Salfred		switch (USB_GET_STATE(xfer)) {
265184610Salfred		case USB_ST_SETUP:
266184610Salfred		case USB_ST_TRANSFERRED:
267184610Salfred			bbb_transfer_start(sc, next_xfer);
268184610Salfred			break;
269184610Salfred		default:
270201681Sthompsa			bbb_done(sc, USB_ERR_STALLED);
271184610Salfred			break;
272184610Salfred		}
273184610Salfred	}
274184610Salfred}
275184610Salfred
276184610Salfredstatic void
277194677Sthompsabbb_command_callback(struct usb_xfer *xfer, usb_error_t error)
278184610Salfred{
279194677Sthompsa	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
280184610Salfred	uint32_t tag;
281184610Salfred
282184610Salfred	switch (USB_GET_STATE(xfer)) {
283184610Salfred	case USB_ST_TRANSFERRED:
284184610Salfred		bbb_transfer_start
285184610Salfred		    (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD :
286184610Salfred		    (sc->dir == DIR_OUT) ? ST_DATA_WR :
287184610Salfred		    ST_STATUS));
288184610Salfred		break;
289184610Salfred
290184610Salfred	case USB_ST_SETUP:
291184610Salfred		sc->status_try = 0;
292259454Shselasky		tag = UGETDW(sc->cbw->dCBWTag) + 1;
293259454Shselasky		USETDW(sc->cbw->dCBWSignature, CBWSIGNATURE);
294259454Shselasky		USETDW(sc->cbw->dCBWTag, tag);
295259454Shselasky		USETDW(sc->cbw->dCBWDataTransferLength, (uint32_t)sc->data_len);
296259454Shselasky		sc->cbw->bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT);
297259454Shselasky		sc->cbw->bCBWLUN = sc->lun;
298259454Shselasky		sc->cbw->bCDBLength = sc->cmd_len;
299259454Shselasky		if (sc->cbw->bCDBLength > sizeof(sc->cbw->CBWCDB)) {
300259454Shselasky			sc->cbw->bCDBLength = sizeof(sc->cbw->CBWCDB);
301199816Sthompsa			DPRINTFN(0, "Truncating long command\n");
302184610Salfred		}
303260575Shselasky		usbd_xfer_set_frame_len(xfer, 0,
304260575Shselasky		    sizeof(struct bbb_cbw));
305194228Sthompsa		usbd_transfer_submit(xfer);
306184610Salfred		break;
307184610Salfred
308184610Salfred	default:			/* Error */
309201681Sthompsa		bbb_done(sc, error);
310184610Salfred		break;
311184610Salfred	}
312184610Salfred}
313184610Salfred
314184610Salfredstatic void
315194677Sthompsabbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error)
316184610Salfred{
317194677Sthompsa	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
318194677Sthompsa	usb_frlength_t max_bulk = usbd_xfer_max_len(xfer);
319194677Sthompsa	int actlen, sumlen;
320184610Salfred
321194677Sthompsa	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
322194677Sthompsa
323184610Salfred	switch (USB_GET_STATE(xfer)) {
324184610Salfred	case USB_ST_TRANSFERRED:
325194677Sthompsa		sc->data_rem -= actlen;
326194677Sthompsa		sc->data_ptr += actlen;
327194677Sthompsa		sc->actlen += actlen;
328184610Salfred
329194677Sthompsa		if (actlen < sumlen) {
330184610Salfred			/* short transfer */
331184610Salfred			sc->data_rem = 0;
332184610Salfred		}
333184610Salfred	case USB_ST_SETUP:
334184610Salfred		DPRINTF("max_bulk=%d, data_rem=%d\n",
335184610Salfred		    max_bulk, sc->data_rem);
336184610Salfred
337184610Salfred		if (sc->data_rem == 0) {
338184610Salfred			bbb_transfer_start(sc, ST_STATUS);
339184610Salfred			break;
340184610Salfred		}
341184610Salfred		if (max_bulk > sc->data_rem) {
342184610Salfred			max_bulk = sc->data_rem;
343184610Salfred		}
344194677Sthompsa		usbd_xfer_set_timeout(xfer, sc->data_timeout);
345194677Sthompsa		usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk);
346194228Sthompsa		usbd_transfer_submit(xfer);
347184610Salfred		break;
348184610Salfred
349184610Salfred	default:			/* Error */
350194677Sthompsa		if (error == USB_ERR_CANCELLED) {
351201681Sthompsa			bbb_done(sc, error);
352184610Salfred		} else {
353184610Salfred			bbb_transfer_start(sc, ST_DATA_RD_CS);
354184610Salfred		}
355184610Salfred		break;
356184610Salfred	}
357184610Salfred}
358184610Salfred
359184610Salfredstatic void
360194677Sthompsabbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error)
361184610Salfred{
362184610Salfred	bbb_data_clear_stall_callback(xfer, ST_STATUS,
363184610Salfred	    ST_DATA_RD);
364184610Salfred}
365184610Salfred
366184610Salfredstatic void
367194677Sthompsabbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error)
368184610Salfred{
369194677Sthompsa	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
370194677Sthompsa	usb_frlength_t max_bulk = usbd_xfer_max_len(xfer);
371194677Sthompsa	int actlen, sumlen;
372184610Salfred
373194677Sthompsa	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
374194677Sthompsa
375184610Salfred	switch (USB_GET_STATE(xfer)) {
376184610Salfred	case USB_ST_TRANSFERRED:
377194677Sthompsa		sc->data_rem -= actlen;
378194677Sthompsa		sc->data_ptr += actlen;
379194677Sthompsa		sc->actlen += actlen;
380184610Salfred
381194677Sthompsa		if (actlen < sumlen) {
382184610Salfred			/* short transfer */
383184610Salfred			sc->data_rem = 0;
384184610Salfred		}
385184610Salfred	case USB_ST_SETUP:
386184610Salfred		DPRINTF("max_bulk=%d, data_rem=%d\n",
387184610Salfred		    max_bulk, sc->data_rem);
388184610Salfred
389184610Salfred		if (sc->data_rem == 0) {
390184610Salfred			bbb_transfer_start(sc, ST_STATUS);
391260575Shselasky			break;
392184610Salfred		}
393184610Salfred		if (max_bulk > sc->data_rem) {
394184610Salfred			max_bulk = sc->data_rem;
395184610Salfred		}
396194677Sthompsa		usbd_xfer_set_timeout(xfer, sc->data_timeout);
397194677Sthompsa		usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk);
398194228Sthompsa		usbd_transfer_submit(xfer);
399260575Shselasky		break;
400184610Salfred
401184610Salfred	default:			/* Error */
402194677Sthompsa		if (error == USB_ERR_CANCELLED) {
403201681Sthompsa			bbb_done(sc, error);
404184610Salfred		} else {
405184610Salfred			bbb_transfer_start(sc, ST_DATA_WR_CS);
406184610Salfred		}
407260575Shselasky		break;
408184610Salfred	}
409184610Salfred}
410184610Salfred
411184610Salfredstatic void
412194677Sthompsabbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error)
413184610Salfred{
414184610Salfred	bbb_data_clear_stall_callback(xfer, ST_STATUS,
415184610Salfred	    ST_DATA_WR);
416184610Salfred}
417184610Salfred
418184610Salfredstatic void
419194677Sthompsabbb_status_callback(struct usb_xfer *xfer, usb_error_t error)
420184610Salfred{
421194677Sthompsa	struct bbb_transfer *sc = usbd_xfer_softc(xfer);
422235000Shselasky	int actlen;
423235000Shselasky	int sumlen;
424184610Salfred
425194677Sthompsa	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
426194677Sthompsa
427184610Salfred	switch (USB_GET_STATE(xfer)) {
428184610Salfred	case USB_ST_TRANSFERRED:
429184610Salfred
430184610Salfred		/* very simple status check */
431184610Salfred
432259454Shselasky		if (actlen < (int)sizeof(struct bbb_csw)) {
433201681Sthompsa			bbb_done(sc, USB_ERR_SHORT_XFER);
434259454Shselasky		} else if (sc->csw->bCSWStatus == CSWSTATUS_GOOD) {
435201681Sthompsa			bbb_done(sc, 0);	/* success */
436184610Salfred		} else {
437201681Sthompsa			bbb_done(sc, ERR_CSW_FAILED);	/* error */
438184610Salfred		}
439184610Salfred		break;
440184610Salfred
441184610Salfred	case USB_ST_SETUP:
442260575Shselasky		usbd_xfer_set_frame_len(xfer, 0,
443260575Shselasky		    sizeof(struct bbb_csw));
444194228Sthompsa		usbd_transfer_submit(xfer);
445184610Salfred		break;
446184610Salfred
447184610Salfred	default:
448201681Sthompsa		DPRINTF("Failed to read CSW: %s, try %d\n",
449194677Sthompsa		    usbd_errstr(error), sc->status_try);
450184610Salfred
451194677Sthompsa		if (error == USB_ERR_CANCELLED || sc->status_try) {
452201681Sthompsa			bbb_done(sc, error);
453184610Salfred		} else {
454184610Salfred			sc->status_try = 1;
455184610Salfred			bbb_transfer_start(sc, ST_DATA_RD_CS);
456184610Salfred		}
457184610Salfred		break;
458184610Salfred	}
459184610Salfred}
460184610Salfred
461184610Salfred/*------------------------------------------------------------------------*
462184610Salfred *	bbb_command_start - execute a SCSI command synchronously
463184610Salfred *
464184610Salfred * Return values
465184610Salfred * 0: Success
466184610Salfred * Else: Failure
467184610Salfred *------------------------------------------------------------------------*/
468225350Shselaskystatic int
469184610Salfredbbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun,
470201681Sthompsa    void *data_ptr, size_t data_len, void *cmd_ptr, size_t cmd_len,
471193045Sthompsa    usb_timeout_t data_timeout)
472184610Salfred{
473184610Salfred	sc->lun = lun;
474184610Salfred	sc->dir = data_len ? dir : DIR_NONE;
475184610Salfred	sc->data_ptr = data_ptr;
476184610Salfred	sc->data_len = data_len;
477184610Salfred	sc->data_rem = data_len;
478184610Salfred	sc->data_timeout = (data_timeout + USB_MS_HZ);
479184610Salfred	sc->actlen = 0;
480184610Salfred	sc->cmd_len = cmd_len;
481259454Shselasky	memset(&sc->cbw->CBWCDB, 0, sizeof(sc->cbw->CBWCDB));
482259454Shselasky	memcpy(&sc->cbw->CBWCDB, cmd_ptr, cmd_len);
483259454Shselasky	DPRINTFN(1, "SCSI cmd = %*D\n", (int)cmd_len, (char *)sc->cbw->CBWCDB, ":");
484184610Salfred
485201681Sthompsa	mtx_lock(&sc->mtx);
486194228Sthompsa	usbd_transfer_start(sc->xfer[sc->state]);
487184610Salfred
488194228Sthompsa	while (usbd_transfer_pending(sc->xfer[sc->state])) {
489194227Sthompsa		cv_wait(&sc->cv, &sc->mtx);
490184610Salfred	}
491201681Sthompsa	mtx_unlock(&sc->mtx);
492184610Salfred	return (sc->error);
493184610Salfred}
494184610Salfred
495201681Sthompsastatic struct bbb_transfer *
496201681Sthompsabbb_attach(struct usb_device *udev, uint8_t iface_index)
497184610Salfred{
498192984Sthompsa	struct usb_interface *iface;
499192984Sthompsa	struct usb_interface_descriptor *id;
500201681Sthompsa	struct bbb_transfer *sc;
501193045Sthompsa	usb_error_t err;
502223512Shselasky	uint8_t do_unlock;
503184610Salfred
504247090Shselasky	/* Prevent re-enumeration */
505247090Shselasky	do_unlock = usbd_enum_lock(udev);
506223512Shselasky
507223512Shselasky	/*
508223512Shselasky	 * Make sure any driver which is hooked up to this interface,
509223512Shselasky	 * like umass is gone:
510223512Shselasky	 */
511223512Shselasky	usb_detach_device(udev, iface_index, 0);
512223512Shselasky
513223512Shselasky	if (do_unlock)
514223512Shselasky		usbd_enum_unlock(udev);
515223512Shselasky
516194228Sthompsa	iface = usbd_get_iface(udev, iface_index);
517201681Sthompsa	if (iface == NULL)
518201681Sthompsa		return (NULL);
519201681Sthompsa
520184610Salfred	id = iface->idesc;
521201681Sthompsa	if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
522201681Sthompsa		return (NULL);
523201681Sthompsa
524184610Salfred	switch (id->bInterfaceSubClass) {
525184610Salfred	case UISUBCLASS_SCSI:
526184610Salfred	case UISUBCLASS_UFI:
527201681Sthompsa	case UISUBCLASS_SFF8020I:
528201681Sthompsa	case UISUBCLASS_SFF8070I:
529184610Salfred		break;
530184610Salfred	default:
531201681Sthompsa		return (NULL);
532184610Salfred	}
533184610Salfred
534184610Salfred	switch (id->bInterfaceProtocol) {
535184610Salfred	case UIPROTO_MASS_BBB_OLD:
536184610Salfred	case UIPROTO_MASS_BBB:
537184610Salfred		break;
538184610Salfred	default:
539201681Sthompsa		return (NULL);
540184610Salfred	}
541184610Salfred
542184610Salfred	sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO);
543184610Salfred	mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF);
544194227Sthompsa	cv_init(&sc->cv, "WBBB");
545184610Salfred
546201681Sthompsa	err = usbd_transfer_setup(udev, &iface_index, sc->xfer, bbb_config,
547184610Salfred	    ST_MAX, sc, &sc->mtx);
548184610Salfred	if (err) {
549201681Sthompsa		bbb_detach(sc);
550201681Sthompsa		return (NULL);
551184610Salfred	}
552259454Shselasky	/* store pointer to DMA buffers */
553259454Shselasky	sc->buffer = usbd_xfer_get_frame_buffer(
554259454Shselasky	    sc->xfer[ST_DATA_RD], 0);
555260575Shselasky	sc->buffer_size =
556260575Shselasky	    usbd_xfer_max_len(sc->xfer[ST_DATA_RD]);
557259454Shselasky	sc->cbw = usbd_xfer_get_frame_buffer(
558259454Shselasky	    sc->xfer[ST_COMMAND], 0);
559259454Shselasky	sc->csw = usbd_xfer_get_frame_buffer(
560259454Shselasky	    sc->xfer[ST_STATUS], 0);
561259454Shselasky
562201681Sthompsa	return (sc);
563201681Sthompsa}
564184610Salfred
565201681Sthompsastatic void
566201681Sthompsabbb_detach(struct bbb_transfer *sc)
567201681Sthompsa{
568201681Sthompsa	usbd_transfer_unsetup(sc->xfer, ST_MAX);
569201681Sthompsa	mtx_destroy(&sc->mtx);
570201681Sthompsa	cv_destroy(&sc->cv);
571201681Sthompsa	free(sc, M_USB);
572201681Sthompsa}
573184610Salfred
574201681Sthompsa/*------------------------------------------------------------------------*
575201681Sthompsa *	usb_iface_is_cdrom
576201681Sthompsa *
577201681Sthompsa * Return values:
578201681Sthompsa * 1: This interface is an auto install disk (CD-ROM)
579201681Sthompsa * 0: Not an auto install disk.
580201681Sthompsa *------------------------------------------------------------------------*/
581201681Sthompsaint
582201681Sthompsausb_iface_is_cdrom(struct usb_device *udev, uint8_t iface_index)
583201681Sthompsa{
584201681Sthompsa	struct bbb_transfer *sc;
585225350Shselasky	uint8_t timeout;
586225350Shselasky	uint8_t is_cdrom;
587201681Sthompsa	uint8_t sid_type;
588225350Shselasky	int err;
589184610Salfred
590201681Sthompsa	sc = bbb_attach(udev, iface_index);
591201681Sthompsa	if (sc == NULL)
592201681Sthompsa		return (0);
593184610Salfred
594201681Sthompsa	is_cdrom = 0;
595201681Sthompsa	timeout = 4;	/* tries */
596201681Sthompsa	while (--timeout) {
597201681Sthompsa		err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
598201681Sthompsa		    SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
599201681Sthompsa		    USB_MS_HZ);
600185290Salfred
601201681Sthompsa		if (err == 0 && sc->actlen > 0) {
602201681Sthompsa			sid_type = sc->buffer[0] & 0x1F;
603201681Sthompsa			if (sid_type == 0x05)
604201681Sthompsa				is_cdrom = 1;
605201681Sthompsa			break;
606201681Sthompsa		} else if (err != ERR_CSW_FAILED)
607201681Sthompsa			break;	/* non retryable error */
608201681Sthompsa		usb_pause_mtx(NULL, hz);
609184610Salfred	}
610201681Sthompsa	bbb_detach(sc);
611201681Sthompsa	return (is_cdrom);
612201681Sthompsa}
613184610Salfred
614227007Shselaskystatic uint8_t
615227007Shselaskyusb_msc_get_max_lun(struct usb_device *udev, uint8_t iface_index)
616227007Shselasky{
617227007Shselasky	struct usb_device_request req;
618227007Shselasky	usb_error_t err;
619227007Shselasky	uint8_t buf = 0;
620227007Shselasky
621227007Shselasky
622227007Shselasky	/* The Get Max Lun command is a class-specific request. */
623227007Shselasky	req.bmRequestType = UT_READ_CLASS_INTERFACE;
624227007Shselasky	req.bRequest = 0xFE;		/* GET_MAX_LUN */
625227007Shselasky	USETW(req.wValue, 0);
626227007Shselasky	req.wIndex[0] = iface_index;
627227007Shselasky	req.wIndex[1] = 0;
628227007Shselasky	USETW(req.wLength, 1);
629227007Shselasky
630227007Shselasky	err = usbd_do_request(udev, NULL, &req, &buf);
631227007Shselasky	if (err)
632227007Shselasky		buf = 0;
633227007Shselasky
634227007Shselasky	return (buf);
635227007Shselasky}
636227007Shselasky
637201681Sthompsausb_error_t
638225350Shselaskyusb_msc_auto_quirk(struct usb_device *udev, uint8_t iface_index)
639225350Shselasky{
640225350Shselasky	struct bbb_transfer *sc;
641225350Shselasky	uint8_t timeout;
642225350Shselasky	uint8_t is_no_direct;
643225350Shselasky	uint8_t sid_type;
644225350Shselasky	int err;
645225350Shselasky
646225350Shselasky	sc = bbb_attach(udev, iface_index);
647225350Shselasky	if (sc == NULL)
648225350Shselasky		return (0);
649225350Shselasky
650225350Shselasky	/*
651225350Shselasky	 * Some devices need a delay after that the configuration
652225350Shselasky	 * value is set to function properly:
653225350Shselasky	 */
654225350Shselasky	usb_pause_mtx(NULL, hz);
655225350Shselasky
656227007Shselasky	if (usb_msc_get_max_lun(udev, iface_index) == 0) {
657227007Shselasky		DPRINTF("Device has only got one LUN.\n");
658227007Shselasky		usbd_add_dynamic_quirk(udev, UQ_MSC_NO_GETMAXLUN);
659227007Shselasky	}
660227007Shselasky
661225350Shselasky	is_no_direct = 1;
662240666Shselasky	for (timeout = 4; timeout != 0; timeout--) {
663225350Shselasky		err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
664225350Shselasky		    SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
665225350Shselasky		    USB_MS_HZ);
666225350Shselasky
667225350Shselasky		if (err == 0 && sc->actlen > 0) {
668225350Shselasky			sid_type = sc->buffer[0] & 0x1F;
669225350Shselasky			if (sid_type == 0x00)
670225350Shselasky				is_no_direct = 0;
671225350Shselasky			break;
672242821Shselasky		} else if (err != ERR_CSW_FAILED) {
673242821Shselasky			DPRINTF("Device is not responding "
674242821Shselasky			    "properly to SCSI INQUIRY command.\n");
675242821Shselasky			goto error;	/* non retryable error */
676242821Shselasky		}
677225350Shselasky		usb_pause_mtx(NULL, hz);
678225350Shselasky	}
679225350Shselasky
680225350Shselasky	if (is_no_direct) {
681225350Shselasky		DPRINTF("Device is not direct access.\n");
682225350Shselasky		goto done;
683225350Shselasky	}
684225350Shselasky
685225350Shselasky	err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
686225350Shselasky	    &scsi_test_unit_ready, sizeof(scsi_test_unit_ready),
687225350Shselasky	    USB_MS_HZ);
688225350Shselasky
689225350Shselasky	if (err != 0) {
690225350Shselasky
691225350Shselasky		if (err != ERR_CSW_FAILED)
692225350Shselasky			goto error;
693225350Shselasky	}
694240666Shselasky	timeout = 1;
695225350Shselasky
696240666Shselaskyretry_sync_cache:
697225350Shselasky	err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
698225350Shselasky	    &scsi_sync_cache, sizeof(scsi_sync_cache),
699225350Shselasky	    USB_MS_HZ);
700225350Shselasky
701225350Shselasky	if (err != 0) {
702225350Shselasky
703225350Shselasky		if (err != ERR_CSW_FAILED)
704225350Shselasky			goto error;
705225350Shselasky
706225350Shselasky		DPRINTF("Device doesn't handle synchronize cache\n");
707225350Shselasky
708225350Shselasky		usbd_add_dynamic_quirk(udev, UQ_MSC_NO_SYNC_CACHE);
709240666Shselasky
710240666Shselasky	} else {
711240666Shselasky
712240666Shselasky		/*
713240666Shselasky		 * Certain Kingston memory sticks fail the first
714240666Shselasky		 * read capacity after a synchronize cache command
715240666Shselasky		 * has been issued. Disable the synchronize cache
716240666Shselasky		 * command for such devices.
717240666Shselasky		 */
718240666Shselasky
719240666Shselasky		err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8,
720240666Shselasky		    &scsi_read_capacity, sizeof(scsi_read_capacity),
721240666Shselasky		    USB_MS_HZ);
722240666Shselasky
723240666Shselasky		if (err != 0) {
724240666Shselasky			if (err != ERR_CSW_FAILED)
725240666Shselasky				goto error;
726240666Shselasky
727240666Shselasky			err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8,
728240666Shselasky			    &scsi_read_capacity, sizeof(scsi_read_capacity),
729240666Shselasky			    USB_MS_HZ);
730240666Shselasky
731240666Shselasky			if (err == 0) {
732240666Shselasky				if (timeout--)
733240666Shselasky					goto retry_sync_cache;
734240666Shselasky
735240666Shselasky				DPRINTF("Device most likely doesn't "
736240666Shselasky				    "handle synchronize cache\n");
737240666Shselasky
738240666Shselasky				usbd_add_dynamic_quirk(udev,
739240666Shselasky				    UQ_MSC_NO_SYNC_CACHE);
740240666Shselasky			} else {
741240666Shselasky				if (err != ERR_CSW_FAILED)
742240666Shselasky					goto error;
743240666Shselasky			}
744240666Shselasky		}
745225350Shselasky	}
746225350Shselasky
747225400Shselasky	/* clear sense status of any failed commands on the device */
748225400Shselasky
749225400Shselasky	err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
750225400Shselasky	    SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
751225400Shselasky	    USB_MS_HZ);
752225400Shselasky
753225400Shselasky	DPRINTF("Inquiry = %d\n", err);
754225400Shselasky
755225400Shselasky	if (err != 0) {
756225400Shselasky
757225400Shselasky		if (err != ERR_CSW_FAILED)
758225400Shselasky			goto error;
759225400Shselasky	}
760225400Shselasky
761225400Shselasky	err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
762225400Shselasky	    SCSI_SENSE_LEN, &scsi_request_sense,
763225400Shselasky	    sizeof(scsi_request_sense), USB_MS_HZ);
764225400Shselasky
765225400Shselasky	DPRINTF("Request sense = %d\n", err);
766225400Shselasky
767225400Shselasky	if (err != 0) {
768225400Shselasky
769225400Shselasky		if (err != ERR_CSW_FAILED)
770225400Shselasky			goto error;
771225400Shselasky	}
772225400Shselasky
773225350Shselaskydone:
774225350Shselasky	bbb_detach(sc);
775225350Shselasky	return (0);
776225350Shselasky
777225350Shselaskyerror:
778225350Shselasky 	bbb_detach(sc);
779225350Shselasky
780225350Shselasky	DPRINTF("Device did not respond, enabling all quirks\n");
781225350Shselasky
782225350Shselasky	usbd_add_dynamic_quirk(udev, UQ_MSC_NO_SYNC_CACHE);
783225350Shselasky	usbd_add_dynamic_quirk(udev, UQ_MSC_NO_TEST_UNIT_READY);
784225350Shselasky
785225350Shselasky	/* Need to re-enumerate the device */
786225350Shselasky	usbd_req_re_enumerate(udev, NULL);
787225350Shselasky
788225350Shselasky	return (USB_ERR_STALLED);
789225350Shselasky}
790225350Shselasky
791225350Shselaskyusb_error_t
792201681Sthompsausb_msc_eject(struct usb_device *udev, uint8_t iface_index, int method)
793201681Sthompsa{
794201681Sthompsa	struct bbb_transfer *sc;
795201681Sthompsa	usb_error_t err;
796201681Sthompsa
797201681Sthompsa	sc = bbb_attach(udev, iface_index);
798201681Sthompsa	if (sc == NULL)
799201681Sthompsa		return (USB_ERR_INVAL);
800201681Sthompsa
801201681Sthompsa	err = 0;
802201681Sthompsa	switch (method) {
803201681Sthompsa	case MSC_EJECT_STOPUNIT:
804201681Sthompsa		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
805201681Sthompsa		    &scsi_test_unit_ready, sizeof(scsi_test_unit_ready),
806201681Sthompsa		    USB_MS_HZ);
807201681Sthompsa		DPRINTF("Test unit ready status: %s\n", usbd_errstr(err));
808201681Sthompsa		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
809201681Sthompsa		    &scsi_start_stop_unit, sizeof(scsi_start_stop_unit),
810201681Sthompsa		    USB_MS_HZ);
811201681Sthompsa		break;
812201681Sthompsa	case MSC_EJECT_REZERO:
813201681Sthompsa		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
814201681Sthompsa		    &scsi_rezero_init, sizeof(scsi_rezero_init),
815201681Sthompsa		    USB_MS_HZ);
816201681Sthompsa		break;
817201681Sthompsa	case MSC_EJECT_ZTESTOR:
818201681Sthompsa		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
819201681Sthompsa		    &scsi_ztestor_eject, sizeof(scsi_ztestor_eject),
820201681Sthompsa		    USB_MS_HZ);
821201681Sthompsa		break;
822201681Sthompsa	case MSC_EJECT_CMOTECH:
823201681Sthompsa		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
824201681Sthompsa		    &scsi_cmotech_eject, sizeof(scsi_cmotech_eject),
825201681Sthompsa		    USB_MS_HZ);
826201681Sthompsa		break;
827203905Sthompsa	case MSC_EJECT_HUAWEI:
828203905Sthompsa		err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
829203905Sthompsa		    &scsi_huawei_eject, sizeof(scsi_huawei_eject),
830203905Sthompsa		    USB_MS_HZ);
831203905Sthompsa		break;
832213480Sglebius	case MSC_EJECT_TCT:
833213717Sglebius		/*
834213717Sglebius		 * TCTMobile needs DIR_IN flag. To get it, we
835213717Sglebius		 * supply a dummy data with the command.
836213717Sglebius		 */
837260575Shselasky		err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
838260575Shselasky		    sc->buffer_size, &scsi_tct_eject,
839213480Sglebius		    sizeof(scsi_tct_eject), USB_MS_HZ);
840213480Sglebius		break;
841201681Sthompsa	default:
842201681Sthompsa		printf("usb_msc_eject: unknown eject method (%d)\n", method);
843201681Sthompsa		break;
844201681Sthompsa	}
845201681Sthompsa	DPRINTF("Eject CD command status: %s\n", usbd_errstr(err));
846201681Sthompsa
847201681Sthompsa	bbb_detach(sc);
848201681Sthompsa	return (0);
849184610Salfred}
850