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