Deleted Added
sdiff udiff text old ( 193074 ) new ( 194227 )
full compact
1/* $FreeBSD: head/sys/dev/usb/usb_msctest.c 194227 2009-06-15 00:33:18Z 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 <dev/usb/usb_mfunc.h>
36#include <dev/usb/usb_error.h>
37#include <dev/usb/usb.h>
38
39#define USB_DEBUG_VAR usb2_debug
40
41#include <dev/usb/usb_core.h>
42#include <dev/usb/usb_busdma.h>
43#include <dev/usb/usb_process.h>
44#include <dev/usb/usb_transfer.h>
45#include <dev/usb/usb_msctest.h>
46#include <dev/usb/usb_debug.h>
47#include <dev/usb/usb_busdma.h>
48#include <dev/usb/usb_device.h>
49#include <dev/usb/usb_request.h>
50#include <dev/usb/usb_util.h>
51#include <dev/usb/usb_lookup.h>
52
53#include <dev/usb/usb_mfunc.h>
54#include <dev/usb/usb_error.h>
55#include <dev/usb/usb.h>
56
57enum {
58 ST_COMMAND,
59 ST_DATA_RD,
60 ST_DATA_RD_CS,
61 ST_DATA_WR,
62 ST_DATA_WR_CS,
63 ST_STATUS,
64 ST_MAX,
65};
66
67enum {
68 DIR_IN,
69 DIR_OUT,
70 DIR_NONE,
71};
72
73#define BULK_SIZE 64 /* dummy */
74
75/* Command Block Wrapper */
76struct bbb_cbw {
77 uDWord dCBWSignature;
78#define CBWSIGNATURE 0x43425355
79 uDWord dCBWTag;
80 uDWord dCBWDataTransferLength;
81 uByte bCBWFlags;
82#define CBWFLAGS_OUT 0x00
83#define CBWFLAGS_IN 0x80
84 uByte bCBWLUN;
85 uByte bCDBLength;
86#define CBWCDBLENGTH 16
87 uByte CBWCDB[CBWCDBLENGTH];
88} __packed;
89
90/* Command Status Wrapper */
91struct bbb_csw {
92 uDWord dCSWSignature;
93#define CSWSIGNATURE 0x53425355
94 uDWord dCSWTag;
95 uDWord dCSWDataResidue;
96 uByte bCSWStatus;
97#define CSWSTATUS_GOOD 0x0
98#define CSWSTATUS_FAILED 0x1
99#define CSWSTATUS_PHASE 0x2
100} __packed;
101
102struct bbb_transfer {
103 struct mtx mtx;
104 struct cv cv;
105 struct bbb_cbw cbw;
106 struct bbb_csw csw;
107
108 struct usb_xfer *xfer[ST_MAX];
109
110 uint8_t *data_ptr;
111
112 usb_size_t data_len; /* bytes */
113 usb_size_t data_rem; /* bytes */
114 usb_timeout_t data_timeout; /* ms */
115 usb_frlength_t actlen; /* bytes */
116
117 uint8_t cmd_len; /* bytes */
118 uint8_t dir;
119 uint8_t lun;
120 uint8_t state;
121 uint8_t error;
122 uint8_t status_try;
123
124 uint8_t buffer[256];
125};
126
127static usb_callback_t bbb_command_callback;
128static usb_callback_t bbb_data_read_callback;
129static usb_callback_t bbb_data_rd_cs_callback;
130static usb_callback_t bbb_data_write_callback;
131static usb_callback_t bbb_data_wr_cs_callback;
132static usb_callback_t bbb_status_callback;
133
134static const struct usb_config bbb_config[ST_MAX] = {
135
136 [ST_COMMAND] = {
137 .type = UE_BULK,
138 .endpoint = UE_ADDR_ANY,
139 .direction = UE_DIR_OUT,
140 .bufsize = sizeof(struct bbb_cbw),
141 .callback = &bbb_command_callback,
142 .timeout = 4 * USB_MS_HZ, /* 4 seconds */
143 },
144
145 [ST_DATA_RD] = {
146 .type = UE_BULK,
147 .endpoint = UE_ADDR_ANY,
148 .direction = UE_DIR_IN,
149 .bufsize = BULK_SIZE,
150 .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,},
151 .callback = &bbb_data_read_callback,
152 .timeout = 4 * USB_MS_HZ, /* 4 seconds */
153 },
154
155 [ST_DATA_RD_CS] = {
156 .type = UE_CONTROL,
157 .endpoint = 0x00, /* Control pipe */
158 .direction = UE_DIR_ANY,
159 .bufsize = sizeof(struct usb_device_request),
160 .callback = &bbb_data_rd_cs_callback,
161 .timeout = 1 * USB_MS_HZ, /* 1 second */
162 },
163
164 [ST_DATA_WR] = {
165 .type = UE_BULK,
166 .endpoint = UE_ADDR_ANY,
167 .direction = UE_DIR_OUT,
168 .bufsize = BULK_SIZE,
169 .flags = {.proxy_buffer = 1,},
170 .callback = &bbb_data_write_callback,
171 .timeout = 4 * USB_MS_HZ, /* 4 seconds */
172 },
173
174 [ST_DATA_WR_CS] = {
175 .type = UE_CONTROL,
176 .endpoint = 0x00, /* Control pipe */
177 .direction = UE_DIR_ANY,
178 .bufsize = sizeof(struct usb_device_request),
179 .callback = &bbb_data_wr_cs_callback,
180 .timeout = 1 * USB_MS_HZ, /* 1 second */
181 },
182
183 [ST_STATUS] = {
184 .type = UE_BULK,
185 .endpoint = UE_ADDR_ANY,
186 .direction = UE_DIR_IN,
187 .bufsize = sizeof(struct bbb_csw),
188 .flags = {.short_xfer_ok = 1,},
189 .callback = &bbb_status_callback,
190 .timeout = 1 * USB_MS_HZ, /* 1 second */
191 },
192};
193
194static void
195bbb_done(struct bbb_transfer *sc, uint8_t error)
196{
197 struct usb_xfer *xfer;
198
199 xfer = sc->xfer[sc->state];
200
201 /* verify the error code */
202
203 if (error) {
204 switch (USB_GET_STATE(xfer)) {
205 case USB_ST_SETUP:
206 case USB_ST_TRANSFERRED:
207 error = 1;
208 break;
209 default:
210 error = 2;
211 break;
212 }
213 }
214 sc->error = error;
215 sc->state = ST_COMMAND;
216 sc->status_try = 1;
217 cv_signal(&sc->cv);
218}
219
220static void
221bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index)
222{
223 sc->state = xfer_index;
224 usb2_transfer_start(sc->xfer[xfer_index]);
225}
226
227static void
228bbb_data_clear_stall_callback(struct usb_xfer *xfer,
229 uint8_t next_xfer, uint8_t stall_xfer)
230{
231 struct bbb_transfer *sc = xfer->priv_sc;
232
233 if (usb2_clear_stall_callback(xfer, sc->xfer[stall_xfer])) {
234 switch (USB_GET_STATE(xfer)) {
235 case USB_ST_SETUP:
236 case USB_ST_TRANSFERRED:
237 bbb_transfer_start(sc, next_xfer);
238 break;
239 default:
240 bbb_done(sc, 1);
241 break;
242 }
243 }
244}
245
246static void
247bbb_command_callback(struct usb_xfer *xfer)
248{
249 struct bbb_transfer *sc = xfer->priv_sc;
250 uint32_t tag;
251
252 switch (USB_GET_STATE(xfer)) {
253 case USB_ST_TRANSFERRED:
254 bbb_transfer_start
255 (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD :
256 (sc->dir == DIR_OUT) ? ST_DATA_WR :
257 ST_STATUS));
258 break;
259
260 case USB_ST_SETUP:
261 sc->status_try = 0;
262 tag = UGETDW(sc->cbw.dCBWTag) + 1;
263 USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE);
264 USETDW(sc->cbw.dCBWTag, tag);
265 USETDW(sc->cbw.dCBWDataTransferLength, (uint32_t)sc->data_len);
266 sc->cbw.bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT);
267 sc->cbw.bCBWLUN = sc->lun;
268 sc->cbw.bCDBLength = sc->cmd_len;
269 if (sc->cbw.bCDBLength > sizeof(sc->cbw.CBWCDB)) {
270 sc->cbw.bCDBLength = sizeof(sc->cbw.CBWCDB);
271 DPRINTFN(0, "Truncating long command!\n");
272 }
273 xfer->frlengths[0] = sizeof(sc->cbw);
274
275 usb2_set_frame_data(xfer, &sc->cbw, 0);
276 usb2_start_hardware(xfer);
277 break;
278
279 default: /* Error */
280 bbb_done(sc, 1);
281 break;
282 }
283}
284
285static void
286bbb_data_read_callback(struct usb_xfer *xfer)
287{
288 struct bbb_transfer *sc = xfer->priv_sc;
289 usb_frlength_t max_bulk = xfer->max_data_length;
290
291 switch (USB_GET_STATE(xfer)) {
292 case USB_ST_TRANSFERRED:
293 sc->data_rem -= xfer->actlen;
294 sc->data_ptr += xfer->actlen;
295 sc->actlen += xfer->actlen;
296
297 if (xfer->actlen < xfer->sumlen) {
298 /* short transfer */
299 sc->data_rem = 0;
300 }
301 case USB_ST_SETUP:
302 DPRINTF("max_bulk=%d, data_rem=%d\n",
303 max_bulk, sc->data_rem);
304
305 if (sc->data_rem == 0) {
306 bbb_transfer_start(sc, ST_STATUS);
307 break;
308 }
309 if (max_bulk > sc->data_rem) {
310 max_bulk = sc->data_rem;
311 }
312 xfer->timeout = sc->data_timeout;
313 xfer->frlengths[0] = max_bulk;
314
315 usb2_set_frame_data(xfer, sc->data_ptr, 0);
316 usb2_start_hardware(xfer);
317 break;
318
319 default: /* Error */
320 if (xfer->error == USB_ERR_CANCELLED) {
321 bbb_done(sc, 1);
322 } else {
323 bbb_transfer_start(sc, ST_DATA_RD_CS);
324 }
325 break;
326 }
327}
328
329static void
330bbb_data_rd_cs_callback(struct usb_xfer *xfer)
331{
332 bbb_data_clear_stall_callback(xfer, ST_STATUS,
333 ST_DATA_RD);
334}
335
336static void
337bbb_data_write_callback(struct usb_xfer *xfer)
338{
339 struct bbb_transfer *sc = xfer->priv_sc;
340 usb_frlength_t max_bulk = xfer->max_data_length;
341
342 switch (USB_GET_STATE(xfer)) {
343 case USB_ST_TRANSFERRED:
344 sc->data_rem -= xfer->actlen;
345 sc->data_ptr += xfer->actlen;
346 sc->actlen += xfer->actlen;
347
348 if (xfer->actlen < xfer->sumlen) {
349 /* short transfer */
350 sc->data_rem = 0;
351 }
352 case USB_ST_SETUP:
353 DPRINTF("max_bulk=%d, data_rem=%d\n",
354 max_bulk, sc->data_rem);
355
356 if (sc->data_rem == 0) {
357 bbb_transfer_start(sc, ST_STATUS);
358 return;
359 }
360 if (max_bulk > sc->data_rem) {
361 max_bulk = sc->data_rem;
362 }
363 xfer->timeout = sc->data_timeout;
364 xfer->frlengths[0] = max_bulk;
365
366 usb2_set_frame_data(xfer, sc->data_ptr, 0);
367 usb2_start_hardware(xfer);
368 return;
369
370 default: /* Error */
371 if (xfer->error == USB_ERR_CANCELLED) {
372 bbb_done(sc, 1);
373 } else {
374 bbb_transfer_start(sc, ST_DATA_WR_CS);
375 }
376 return;
377
378 }
379}
380
381static void
382bbb_data_wr_cs_callback(struct usb_xfer *xfer)
383{
384 bbb_data_clear_stall_callback(xfer, ST_STATUS,
385 ST_DATA_WR);
386}
387
388static void
389bbb_status_callback(struct usb_xfer *xfer)
390{
391 struct bbb_transfer *sc = xfer->priv_sc;
392
393 switch (USB_GET_STATE(xfer)) {
394 case USB_ST_TRANSFERRED:
395
396 /* very simple status check */
397
398 if (xfer->actlen < sizeof(sc->csw)) {
399 bbb_done(sc, 1);/* error */
400 } else if (sc->csw.bCSWStatus == CSWSTATUS_GOOD) {
401 bbb_done(sc, 0);/* success */
402 } else {
403 bbb_done(sc, 1);/* error */
404 }
405 break;
406
407 case USB_ST_SETUP:
408 xfer->frlengths[0] = sizeof(sc->csw);
409
410 usb2_set_frame_data(xfer, &sc->csw, 0);
411 usb2_start_hardware(xfer);
412 break;
413
414 default:
415 DPRINTFN(0, "Failed to read CSW: %s, try %d\n",
416 usb2_errstr(xfer->error), sc->status_try);
417
418 if ((xfer->error == USB_ERR_CANCELLED) ||
419 (sc->status_try)) {
420 bbb_done(sc, 1);
421 } else {
422 sc->status_try = 1;
423 bbb_transfer_start(sc, ST_DATA_RD_CS);
424 }
425 break;
426 }
427}
428
429/*------------------------------------------------------------------------*
430 * bbb_command_start - execute a SCSI command synchronously
431 *
432 * Return values
433 * 0: Success
434 * Else: Failure
435 *------------------------------------------------------------------------*/
436static uint8_t
437bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun,
438 void *data_ptr, usb_size_t data_len, uint8_t cmd_len,
439 usb_timeout_t data_timeout)
440{
441 sc->lun = lun;
442 sc->dir = data_len ? dir : DIR_NONE;
443 sc->data_ptr = data_ptr;
444 sc->data_len = data_len;
445 sc->data_rem = data_len;
446 sc->data_timeout = (data_timeout + USB_MS_HZ);
447 sc->actlen = 0;
448 sc->cmd_len = cmd_len;
449
450 usb2_transfer_start(sc->xfer[sc->state]);
451
452 while (usb2_transfer_pending(sc->xfer[sc->state])) {
453 cv_wait(&sc->cv, &sc->mtx);
454 }
455 return (sc->error);
456}
457
458/*------------------------------------------------------------------------*
459 * usb2_test_autoinstall
460 *
461 * Return values:
462 * 0: This interface is an auto install disk (CD-ROM)
463 * Else: Not an auto install disk.
464 *------------------------------------------------------------------------*/
465usb_error_t
466usb2_test_autoinstall(struct usb_device *udev, uint8_t iface_index,
467 uint8_t do_eject)
468{
469 struct usb_interface *iface;
470 struct usb_interface_descriptor *id;
471 usb_error_t err;
472 uint8_t timeout;
473 uint8_t sid_type;
474 struct bbb_transfer *sc;
475
476 if (udev == NULL) {
477 return (USB_ERR_INVAL);
478 }
479 iface = usb2_get_iface(udev, iface_index);
480 if (iface == NULL) {
481 return (USB_ERR_INVAL);
482 }
483 id = iface->idesc;
484 if (id == NULL) {
485 return (USB_ERR_INVAL);
486 }
487 if (id->bInterfaceClass != UICLASS_MASS) {
488 return (USB_ERR_INVAL);
489 }
490 switch (id->bInterfaceSubClass) {
491 case UISUBCLASS_SCSI:
492 case UISUBCLASS_UFI:
493 break;
494 default:
495 return (USB_ERR_INVAL);
496 }
497
498 switch (id->bInterfaceProtocol) {
499 case UIPROTO_MASS_BBB_OLD:
500 case UIPROTO_MASS_BBB:
501 break;
502 default:
503 return (USB_ERR_INVAL);
504 }
505
506 sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO);
507 if (sc == NULL) {
508 return (USB_ERR_NOMEM);
509 }
510 mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF);
511 cv_init(&sc->cv, "WBBB");
512
513 err = usb2_transfer_setup(udev,
514 &iface_index, sc->xfer, bbb_config,
515 ST_MAX, sc, &sc->mtx);
516
517 if (err) {
518 goto done;
519 }
520 mtx_lock(&sc->mtx);
521
522 timeout = 4; /* tries */
523
524repeat_inquiry:
525
526 sc->cbw.CBWCDB[0] = 0x12; /* INQUIRY */
527 sc->cbw.CBWCDB[1] = 0;
528 sc->cbw.CBWCDB[2] = 0;
529 sc->cbw.CBWCDB[3] = 0;
530 sc->cbw.CBWCDB[4] = 0x24; /* length */
531 sc->cbw.CBWCDB[5] = 0;
532 err = bbb_command_start(sc, DIR_IN, 0,
533 sc->buffer, 0x24, 6, USB_MS_HZ);
534
535 if ((sc->actlen != 0) && (err == 0)) {
536 sid_type = sc->buffer[0] & 0x1F;
537 if (sid_type == 0x05) {
538 /* CD-ROM */
539 if (do_eject) {
540 /* 0: opcode: SCSI START/STOP */
541 sc->cbw.CBWCDB[0] = 0x1b;
542 /* 1: byte2: Not immediate */
543 sc->cbw.CBWCDB[1] = 0x00;
544 /* 2..3: reserved */
545 sc->cbw.CBWCDB[2] = 0x00;
546 sc->cbw.CBWCDB[3] = 0x00;
547 /* 4: Load/Eject command */
548 sc->cbw.CBWCDB[4] = 0x02;
549 /* 5: control */
550 sc->cbw.CBWCDB[5] = 0x00;
551 err = bbb_command_start(sc, DIR_OUT, 0,
552 NULL, 0, 6, USB_MS_HZ);
553
554 DPRINTFN(0, "Eject CD command "
555 "status: %s\n", usb2_errstr(err));
556 }
557 err = 0;
558 goto done;
559 }
560 } else if ((err != 2) && --timeout) {
561 usb2_pause_mtx(&sc->mtx, hz);
562 goto repeat_inquiry;
563 }
564 err = USB_ERR_INVAL;
565 goto done;
566
567done:
568 mtx_unlock(&sc->mtx);
569 usb2_transfer_unsetup(sc->xfer, ST_MAX);
570 mtx_destroy(&sc->mtx);
571 cv_destroy(&sc->cv);
572 free(sc, M_USB);
573 return (err);
574}