diff --git a/sys/dev/usb/storage/umass.c b/sys/dev/usb/storage/umass.c index 8260226e5d12..b8b502494264 100644 --- a/sys/dev/usb/storage/umass.c +++ b/sys/dev/usb/storage/umass.c @@ -1,3021 +1,3028 @@ #include __FBSDID("$FreeBSD$"); /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1999 MAEKAWA Masahide , * Nick Hibma * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ * $NetBSD: umass.c,v 1.28 2000/04/02 23:46:53 augustss Exp $ */ /* Also already merged from NetBSD: * $NetBSD: umass.c,v 1.67 2001/11/25 19:05:22 augustss Exp $ * $NetBSD: umass.c,v 1.90 2002/11/04 19:17:33 pooka Exp $ * $NetBSD: umass.c,v 1.108 2003/11/07 17:03:25 wiz Exp $ * $NetBSD: umass.c,v 1.109 2003/12/04 13:57:31 keihan Exp $ */ /* * Universal Serial Bus Mass Storage Class specs: * http://www.usb.org/developers/devclass_docs/usb_msc_overview_1.2.pdf * http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf * http://www.usb.org/developers/devclass_docs/usb_msc_cbi_1.1.pdf * http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf */ /* * Ported to NetBSD by Lennart Augustsson . * Parts of the code written by Jason R. Thorpe . */ /* * The driver handles 3 Wire Protocols * - Command/Bulk/Interrupt (CBI) * - Command/Bulk/Interrupt with Command Completion Interrupt (CBI with CCI) * - Mass Storage Bulk-Only (BBB) * (BBB refers Bulk/Bulk/Bulk for Command/Data/Status phases) * * Over these wire protocols it handles the following command protocols * - SCSI * - UFI (floppy command set) * - 8070i (ATAPI) * * UFI and 8070i (ATAPI) are transformed versions of the SCSI command set. The * sc->sc_transform method is used to convert the commands into the appropriate * format (if at all necessary). For example, UFI requires all commands to be * 12 bytes in length amongst other things. * * The source code below is marked and can be split into a number of pieces * (in this order): * * - probe/attach/detach * - generic transfer routines * - BBB * - CBI * - CBI_I (in addition to functions from CBI) * - CAM (Common Access Method) * - SCSI * - UFI * - 8070i (ATAPI) * * The protocols are implemented using a state machine, for the transfers as * well as for the resets. The state machine is contained in umass_t_*_callback. * The state machine is started through either umass_command_start() or * umass_reset(). * * The reason for doing this is a) CAM performs a lot better this way and b) it * avoids using tsleep from interrupt context (for example after a failed * transfer). */ /* * The SCSI related part of this driver has been derived from the * dev/ppbus/vpo.c driver, by Nicolas Souchu (nsouch@FreeBSD.org). * * The CAM layer uses so called actions which are messages sent to the host * adapter for completion. The actions come in through umass_cam_action. The * appropriate block of routines is called depending on the transport protocol * in use. When the transfer has finished, these routines call * umass_cam_cb again to complete the CAM command. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #include #include #include #ifdef USB_DEBUG #define DIF(m, x) \ do { \ if (umass_debug & (m)) { x ; } \ } while (0) #define DPRINTF(sc, m, fmt, ...) \ do { \ if (umass_debug & (m)) { \ printf("%s:%s: " fmt, \ (sc) ? (const char *)(sc)->sc_name : \ (const char *)"umassX", \ __FUNCTION__ ,## __VA_ARGS__); \ } \ } while (0) #define UDMASS_GEN 0x00010000 /* general */ #define UDMASS_SCSI 0x00020000 /* scsi */ #define UDMASS_UFI 0x00040000 /* ufi command set */ #define UDMASS_ATAPI 0x00080000 /* 8070i command set */ #define UDMASS_CMD (UDMASS_SCSI|UDMASS_UFI|UDMASS_ATAPI) #define UDMASS_USB 0x00100000 /* USB general */ #define UDMASS_BBB 0x00200000 /* Bulk-Only transfers */ #define UDMASS_CBI 0x00400000 /* CBI transfers */ #define UDMASS_WIRE (UDMASS_BBB|UDMASS_CBI) #define UDMASS_ALL 0xffff0000 /* all of the above */ static int umass_debug; static int umass_throttle; static SYSCTL_NODE(_hw_usb, OID_AUTO, umass, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB umass"); SYSCTL_INT(_hw_usb_umass, OID_AUTO, debug, CTLFLAG_RWTUN, &umass_debug, 0, "umass debug level"); SYSCTL_INT(_hw_usb_umass, OID_AUTO, throttle, CTLFLAG_RWTUN, &umass_throttle, 0, "Forced delay between commands in milliseconds"); #else #define DIF(...) do { } while (0) #define DPRINTF(...) do { } while (0) #endif #define UMASS_BULK_SIZE (1 << 17) #define UMASS_CBI_DIAGNOSTIC_CMDLEN 12 /* bytes */ #define UMASS_MAX_CMDLEN MAX(12, CAM_MAX_CDBLEN) /* bytes */ /* USB transfer definitions */ #define UMASS_T_BBB_RESET1 0 /* Bulk-Only */ #define UMASS_T_BBB_RESET2 1 #define UMASS_T_BBB_RESET3 2 #define UMASS_T_BBB_COMMAND 3 #define UMASS_T_BBB_DATA_READ 4 #define UMASS_T_BBB_DATA_RD_CS 5 #define UMASS_T_BBB_DATA_WRITE 6 #define UMASS_T_BBB_DATA_WR_CS 7 #define UMASS_T_BBB_STATUS 8 #define UMASS_T_BBB_MAX 9 #define UMASS_T_CBI_RESET1 0 /* CBI */ #define UMASS_T_CBI_RESET2 1 #define UMASS_T_CBI_RESET3 2 #define UMASS_T_CBI_COMMAND 3 #define UMASS_T_CBI_DATA_READ 4 #define UMASS_T_CBI_DATA_RD_CS 5 #define UMASS_T_CBI_DATA_WRITE 6 #define UMASS_T_CBI_DATA_WR_CS 7 #define UMASS_T_CBI_STATUS 8 #define UMASS_T_CBI_RESET4 9 #define UMASS_T_CBI_MAX 10 #define UMASS_T_MAX MAX(UMASS_T_CBI_MAX, UMASS_T_BBB_MAX) /* Generic definitions */ /* Direction for transfer */ #define DIR_NONE 0 #define DIR_IN 1 #define DIR_OUT 2 /* device name */ #define DEVNAME "umass" #define DEVNAME_SIM "umass-sim" /* Approximate maximum transfer speeds (assumes 33% overhead). */ #define UMASS_FULL_TRANSFER_SPEED 1000 #define UMASS_HIGH_TRANSFER_SPEED 40000 #define UMASS_SUPER_TRANSFER_SPEED 400000 #define UMASS_FLOPPY_TRANSFER_SPEED 20 #define UMASS_TIMEOUT 5000 /* ms */ /* CAM specific definitions */ #define UMASS_SCSIID_MAX 1 /* maximum number of drives expected */ #define UMASS_SCSIID_HOST UMASS_SCSIID_MAX /* Bulk-Only features */ #define UR_BBB_RESET 0xff /* Bulk-Only reset */ #define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */ /* Command Block Wrapper */ typedef struct { uDWord dCBWSignature; #define CBWSIGNATURE 0x43425355 uDWord dCBWTag; uDWord dCBWDataTransferLength; uByte bCBWFlags; #define CBWFLAGS_OUT 0x00 #define CBWFLAGS_IN 0x80 uByte bCBWLUN; uByte bCDBLength; #define CBWCDBLENGTH 16 uByte CBWCDB[CBWCDBLENGTH]; } __packed umass_bbb_cbw_t; #define UMASS_BBB_CBW_SIZE 31 /* Command Status Wrapper */ typedef struct { uDWord dCSWSignature; #define CSWSIGNATURE 0x53425355 #define CSWSIGNATURE_IMAGINATION_DBX1 0x43425355 #define CSWSIGNATURE_OLYMPUS_C1 0x55425355 uDWord dCSWTag; uDWord dCSWDataResidue; uByte bCSWStatus; #define CSWSTATUS_GOOD 0x0 #define CSWSTATUS_FAILED 0x1 #define CSWSTATUS_PHASE 0x2 } __packed umass_bbb_csw_t; #define UMASS_BBB_CSW_SIZE 13 /* CBI features */ #define UR_CBI_ADSC 0x00 typedef union { struct { uint8_t type; #define IDB_TYPE_CCI 0x00 uint8_t value; #define IDB_VALUE_PASS 0x00 #define IDB_VALUE_FAIL 0x01 #define IDB_VALUE_PHASE 0x02 #define IDB_VALUE_PERSISTENT 0x03 #define IDB_VALUE_STATUS_MASK 0x03 } __packed common; struct { uint8_t asc; uint8_t ascq; } __packed ufi; } __packed umass_cbi_sbl_t; struct umass_softc; /* see below */ typedef void (umass_callback_t)(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status); #define STATUS_CMD_OK 0 /* everything ok */ #define STATUS_CMD_UNKNOWN 1 /* will have to fetch sense */ #define STATUS_CMD_FAILED 2 /* transfer was ok, command failed */ #define STATUS_WIRE_FAILED 3 /* couldn't even get command across */ typedef uint8_t (umass_transform_t)(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len); /* Wire and command protocol */ #define UMASS_PROTO_BBB 0x0001 /* USB wire protocol */ #define UMASS_PROTO_CBI 0x0002 #define UMASS_PROTO_CBI_I 0x0004 #define UMASS_PROTO_WIRE 0x00ff /* USB wire protocol mask */ #define UMASS_PROTO_SCSI 0x0100 /* command protocol */ #define UMASS_PROTO_ATAPI 0x0200 #define UMASS_PROTO_UFI 0x0400 #define UMASS_PROTO_RBC 0x0800 #define UMASS_PROTO_COMMAND 0xff00 /* command protocol mask */ /* Device specific quirks */ #define NO_QUIRKS 0x0000 /* * The drive does not support Test Unit Ready. Convert to Start Unit */ #define NO_TEST_UNIT_READY 0x0001 /* * The drive does not reset the Unit Attention state after REQUEST * SENSE has been sent. The INQUIRY command does not reset the UA * either, and so CAM runs in circles trying to retrieve the initial * INQUIRY data. */ #define RS_NO_CLEAR_UA 0x0002 /* The drive does not support START STOP. */ #define NO_START_STOP 0x0004 /* Don't ask for full inquiry data (255b). */ #define FORCE_SHORT_INQUIRY 0x0008 /* Needs to be initialised the Shuttle way */ #define SHUTTLE_INIT 0x0010 /* Drive needs to be switched to alternate iface 1 */ #define ALT_IFACE_1 0x0020 /* Drive does not do 1Mb/s, but just floppy speeds (20kb/s) */ #define FLOPPY_SPEED 0x0040 /* The device can't count and gets the residue of transfers wrong */ #define IGNORE_RESIDUE 0x0080 /* No GetMaxLun call */ #define NO_GETMAXLUN 0x0100 /* The device uses a weird CSWSIGNATURE. */ #define WRONG_CSWSIG 0x0200 /* Device cannot handle INQUIRY so fake a generic response */ #define NO_INQUIRY 0x0400 /* Device cannot handle INQUIRY EVPD, return CHECK CONDITION */ #define NO_INQUIRY_EVPD 0x0800 /* Pad all RBC requests to 12 bytes. */ #define RBC_PAD_TO_12 0x1000 /* * Device reports number of sectors from READ_CAPACITY, not max * sector number. */ #define READ_CAPACITY_OFFBY1 0x2000 /* * Device cannot handle a SCSI synchronize cache command. Normally * this quirk would be handled in the cam layer, but for IDE bridges * we need to associate the quirk with the bridge and not the * underlying disk device. This is handled by faking a success * result. */ #define NO_SYNCHRONIZE_CACHE 0x4000 /* Device does not support 'PREVENT/ALLOW MEDIUM REMOVAL'. */ #define NO_PREVENT_ALLOW 0x8000 struct umass_softc { struct scsi_sense cam_scsi_sense; struct scsi_test_unit_ready cam_scsi_test_unit_ready; struct mtx sc_mtx; struct { uint8_t *data_ptr; union ccb *ccb; umass_callback_t *callback; uint32_t data_len; /* bytes */ uint32_t data_rem; /* bytes */ uint32_t data_timeout; /* ms */ uint32_t actlen; /* bytes */ uint8_t cmd_data[UMASS_MAX_CMDLEN]; uint8_t cmd_len; /* bytes */ uint8_t dir; uint8_t lun; } sc_transfer; /* Bulk specific variables for transfers in progress */ umass_bbb_cbw_t cbw; /* command block wrapper */ umass_bbb_csw_t csw; /* command status wrapper */ /* CBI specific variables for transfers in progress */ umass_cbi_sbl_t sbl; /* status block */ device_t sc_dev; struct usb_device *sc_udev; struct cam_sim *sc_sim; /* SCSI Interface Module */ struct usb_xfer *sc_xfer[UMASS_T_MAX]; /* * The command transform function is used to convert the SCSI * commands into their derivatives, like UFI, ATAPI, and friends. */ umass_transform_t *sc_transform; uint32_t sc_unit; uint32_t sc_quirks; /* they got it almost right */ uint32_t sc_proto; /* wire and cmd protocol */ uint8_t sc_name[16]; uint8_t sc_iface_no; /* interface number */ uint8_t sc_maxlun; /* maximum LUN number, inclusive */ uint8_t sc_last_xfer_index; uint8_t sc_status_try; }; struct umass_probe_proto { uint32_t quirks; uint32_t proto; int error; }; /* prototypes */ static device_probe_t umass_probe; static device_attach_t umass_attach; static device_detach_t umass_detach; static usb_callback_t umass_tr_error; static usb_callback_t umass_t_bbb_reset1_callback; static usb_callback_t umass_t_bbb_reset2_callback; static usb_callback_t umass_t_bbb_reset3_callback; static usb_callback_t umass_t_bbb_command_callback; static usb_callback_t umass_t_bbb_data_read_callback; static usb_callback_t umass_t_bbb_data_rd_cs_callback; static usb_callback_t umass_t_bbb_data_write_callback; static usb_callback_t umass_t_bbb_data_wr_cs_callback; static usb_callback_t umass_t_bbb_status_callback; static usb_callback_t umass_t_cbi_reset1_callback; static usb_callback_t umass_t_cbi_reset2_callback; static usb_callback_t umass_t_cbi_reset3_callback; static usb_callback_t umass_t_cbi_reset4_callback; static usb_callback_t umass_t_cbi_command_callback; static usb_callback_t umass_t_cbi_data_read_callback; static usb_callback_t umass_t_cbi_data_rd_cs_callback; static usb_callback_t umass_t_cbi_data_write_callback; static usb_callback_t umass_t_cbi_data_wr_cs_callback; static usb_callback_t umass_t_cbi_status_callback; static void umass_cancel_ccb(struct umass_softc *); static void umass_init_shuttle(struct umass_softc *); static void umass_reset(struct umass_softc *); static void umass_t_bbb_data_clear_stall_callback(struct usb_xfer *, uint8_t, uint8_t, usb_error_t); static void umass_command_start(struct umass_softc *, uint8_t, void *, uint32_t, uint32_t, umass_callback_t *, union ccb *); static uint8_t umass_bbb_get_max_lun(struct umass_softc *); static void umass_cbi_start_status(struct umass_softc *); static void umass_t_cbi_data_clear_stall_callback(struct usb_xfer *, uint8_t, uint8_t, usb_error_t); static int umass_cam_attach_sim(struct umass_softc *); static void umass_cam_attach(struct umass_softc *); static void umass_cam_detach_sim(struct umass_softc *); static void umass_cam_action(struct cam_sim *, union ccb *); static void umass_cam_poll(struct cam_sim *); static void umass_cam_cb(struct umass_softc *, union ccb *, uint32_t, uint8_t); static void umass_cam_sense_cb(struct umass_softc *, union ccb *, uint32_t, uint8_t); static void umass_cam_quirk_cb(struct umass_softc *, union ccb *, uint32_t, uint8_t); static uint8_t umass_scsi_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_rbc_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_ufi_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_atapi_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_no_transform(struct umass_softc *, uint8_t *, uint8_t); static uint8_t umass_std_transform(struct umass_softc *, union ccb *, uint8_t *, uint8_t); #ifdef USB_DEBUG static void umass_bbb_dump_cbw(struct umass_softc *, umass_bbb_cbw_t *); static void umass_bbb_dump_csw(struct umass_softc *, umass_bbb_csw_t *); static void umass_cbi_dump_cmd(struct umass_softc *, void *, uint8_t); static void umass_dump_buffer(struct umass_softc *, uint8_t *, uint32_t, uint32_t); #endif static struct usb_config umass_bbb_config[UMASS_T_BBB_MAX] = { [UMASS_T_BBB_RESET1] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_reset1_callback, .timeout = 5000, /* 5 seconds */ .interval = 500, /* 500 milliseconds */ }, [UMASS_T_BBB_RESET2] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_reset2_callback, .timeout = 5000, /* 5 seconds */ .interval = 50, /* 50 milliseconds */ }, [UMASS_T_BBB_RESET3] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_reset3_callback, .timeout = 5000, /* 5 seconds */ .interval = 50, /* 50 milliseconds */ }, [UMASS_T_BBB_COMMAND] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = sizeof(umass_bbb_cbw_t), .callback = &umass_t_bbb_command_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_BBB_DATA_READ] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UMASS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,}, .callback = &umass_t_bbb_data_read_callback, .timeout = 0, /* overwritten later */ }, [UMASS_T_BBB_DATA_RD_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_data_rd_cs_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_BBB_DATA_WRITE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMASS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,}, .callback = &umass_t_bbb_data_write_callback, .timeout = 0, /* overwritten later */ }, [UMASS_T_BBB_DATA_WR_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_bbb_data_wr_cs_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_BBB_STATUS] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = sizeof(umass_bbb_csw_t), .flags = {.short_xfer_ok = 1,}, .callback = &umass_t_bbb_status_callback, .timeout = 5000, /* ms */ }, }; static struct usb_config umass_cbi_config[UMASS_T_CBI_MAX] = { [UMASS_T_CBI_RESET1] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + UMASS_CBI_DIAGNOSTIC_CMDLEN), .callback = &umass_t_cbi_reset1_callback, .timeout = 5000, /* 5 seconds */ .interval = 500, /* 500 milliseconds */ }, [UMASS_T_CBI_RESET2] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_reset2_callback, .timeout = 5000, /* 5 seconds */ .interval = 50, /* 50 milliseconds */ }, [UMASS_T_CBI_RESET3] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_reset3_callback, .timeout = 5000, /* 5 seconds */ .interval = 50, /* 50 milliseconds */ }, [UMASS_T_CBI_COMMAND] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + UMASS_MAX_CMDLEN), .callback = &umass_t_cbi_command_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_CBI_DATA_READ] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UMASS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,}, .callback = &umass_t_cbi_data_read_callback, .timeout = 0, /* overwritten later */ }, [UMASS_T_CBI_DATA_RD_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_data_rd_cs_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_CBI_DATA_WRITE] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMASS_BULK_SIZE, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,}, .callback = &umass_t_cbi_data_write_callback, .timeout = 0, /* overwritten later */ }, [UMASS_T_CBI_DATA_WR_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_data_wr_cs_callback, .timeout = 5000, /* 5 seconds */ }, [UMASS_T_CBI_STATUS] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.short_xfer_ok = 1,.no_pipe_ok = 1,}, .bufsize = sizeof(umass_cbi_sbl_t), .callback = &umass_t_cbi_status_callback, .timeout = 5000, /* ms */ }, [UMASS_T_CBI_RESET4] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &umass_t_cbi_reset4_callback, .timeout = 5000, /* ms */ }, }; /* If device cannot return valid inquiry data, fake it */ static const uint8_t fake_inq_data[SHORT_INQUIRY_LENGTH] = { 0, /* removable */ 0x80, SCSI_REV_2, SCSI_REV_2, /* additional_length */ 31, 0, 0, 0 }; #define UFI_COMMAND_LENGTH 12 /* UFI commands are always 12 bytes */ #define ATAPI_COMMAND_LENGTH 12 /* ATAPI commands are always 12 bytes */ static devclass_t umass_devclass; static device_method_t umass_methods[] = { /* Device interface */ DEVMETHOD(device_probe, umass_probe), DEVMETHOD(device_attach, umass_attach), DEVMETHOD(device_detach, umass_detach), DEVMETHOD_END }; static driver_t umass_driver = { .name = "umass", .methods = umass_methods, .size = sizeof(struct umass_softc), }; static const STRUCT_USB_HOST_ID __used umass_devs[] = { /* generic mass storage class */ {USB_IFACE_CLASS(UICLASS_MASS),}, }; DRIVER_MODULE(umass, uhub, umass_driver, umass_devclass, NULL, 0); MODULE_DEPEND(umass, usb, 1, 1, 1); MODULE_DEPEND(umass, cam, 1, 1, 1); MODULE_VERSION(umass, 1); USB_PNP_HOST_INFO(umass_devs); /* * USB device probe/attach/detach */ static uint16_t umass_get_proto(struct usb_interface *iface) { struct usb_interface_descriptor *id; uint16_t retval; retval = 0; /* Check for a standards compliant device */ id = usbd_get_interface_descriptor(iface); if ((id == NULL) || (id->bInterfaceClass != UICLASS_MASS)) { goto done; } switch (id->bInterfaceSubClass) { case UISUBCLASS_SCSI: retval |= UMASS_PROTO_SCSI; break; case UISUBCLASS_UFI: retval |= UMASS_PROTO_UFI; break; case UISUBCLASS_RBC: retval |= UMASS_PROTO_RBC; break; case UISUBCLASS_SFF8020I: case UISUBCLASS_SFF8070I: retval |= UMASS_PROTO_ATAPI; break; default: goto done; } switch (id->bInterfaceProtocol) { case UIPROTO_MASS_CBI: retval |= UMASS_PROTO_CBI; break; case UIPROTO_MASS_CBI_I: retval |= UMASS_PROTO_CBI_I; break; case UIPROTO_MASS_BBB_OLD: case UIPROTO_MASS_BBB: retval |= UMASS_PROTO_BBB; break; default: goto done; } done: return (retval); } /* * Match the device we are seeing with the devices supported. */ static struct umass_probe_proto umass_probe_proto(device_t dev, struct usb_attach_arg *uaa) { struct umass_probe_proto ret; uint32_t quirks = NO_QUIRKS; uint32_t proto = umass_get_proto(uaa->iface); memset(&ret, 0, sizeof(ret)); ret.error = BUS_PROBE_GENERIC; /* Check if we should deny probing. */ if (usb_test_quirk(uaa, UQ_MSC_IGNORE)) { ret.error = ENXIO; goto done; } /* Search for protocol enforcement */ if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_BBB)) { proto &= ~UMASS_PROTO_WIRE; proto |= UMASS_PROTO_BBB; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI)) { proto &= ~UMASS_PROTO_WIRE; proto |= UMASS_PROTO_CBI; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI_I)) { proto &= ~UMASS_PROTO_WIRE; proto |= UMASS_PROTO_CBI_I; } if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_SCSI)) { proto &= ~UMASS_PROTO_COMMAND; proto |= UMASS_PROTO_SCSI; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_ATAPI)) { proto &= ~UMASS_PROTO_COMMAND; proto |= UMASS_PROTO_ATAPI; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_UFI)) { proto &= ~UMASS_PROTO_COMMAND; proto |= UMASS_PROTO_UFI; } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_RBC)) { proto &= ~UMASS_PROTO_COMMAND; proto |= UMASS_PROTO_RBC; } /* Check if the protocol is invalid */ if ((proto & UMASS_PROTO_COMMAND) == 0) { ret.error = ENXIO; goto done; } if ((proto & UMASS_PROTO_WIRE) == 0) { ret.error = ENXIO; goto done; } /* Search for quirks */ if (usb_test_quirk(uaa, UQ_MSC_NO_TEST_UNIT_READY)) quirks |= NO_TEST_UNIT_READY; if (usb_test_quirk(uaa, UQ_MSC_NO_RS_CLEAR_UA)) quirks |= RS_NO_CLEAR_UA; if (usb_test_quirk(uaa, UQ_MSC_NO_START_STOP)) quirks |= NO_START_STOP; if (usb_test_quirk(uaa, UQ_MSC_NO_GETMAXLUN)) quirks |= NO_GETMAXLUN; if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY)) quirks |= NO_INQUIRY; if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY_EVPD)) quirks |= NO_INQUIRY_EVPD; if (usb_test_quirk(uaa, UQ_MSC_NO_PREVENT_ALLOW)) quirks |= NO_PREVENT_ALLOW; if (usb_test_quirk(uaa, UQ_MSC_NO_SYNC_CACHE)) quirks |= NO_SYNCHRONIZE_CACHE; if (usb_test_quirk(uaa, UQ_MSC_SHUTTLE_INIT)) quirks |= SHUTTLE_INIT; if (usb_test_quirk(uaa, UQ_MSC_ALT_IFACE_1)) quirks |= ALT_IFACE_1; if (usb_test_quirk(uaa, UQ_MSC_FLOPPY_SPEED)) quirks |= FLOPPY_SPEED; if (usb_test_quirk(uaa, UQ_MSC_IGNORE_RESIDUE)) quirks |= IGNORE_RESIDUE; if (usb_test_quirk(uaa, UQ_MSC_WRONG_CSWSIG)) quirks |= WRONG_CSWSIG; if (usb_test_quirk(uaa, UQ_MSC_RBC_PAD_TO_12)) quirks |= RBC_PAD_TO_12; if (usb_test_quirk(uaa, UQ_MSC_READ_CAP_OFFBY1)) quirks |= READ_CAPACITY_OFFBY1; if (usb_test_quirk(uaa, UQ_MSC_FORCE_SHORT_INQ)) quirks |= FORCE_SHORT_INQUIRY; done: ret.quirks = quirks; ret.proto = proto; return (ret); } static int umass_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct umass_probe_proto temp; if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } temp = umass_probe_proto(dev, uaa); return (temp.error); } static int umass_attach(device_t dev) { struct umass_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct umass_probe_proto temp = umass_probe_proto(dev, uaa); struct usb_interface_descriptor *id; int err; /* * NOTE: the softc struct is cleared in device_set_driver. * We can safely call umass_detach without specifically * initializing the struct. */ sc->sc_dev = dev; sc->sc_udev = uaa->device; sc->sc_proto = temp.proto; sc->sc_quirks = temp.quirks; sc->sc_unit = device_get_unit(dev); snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", device_get_nameunit(dev)); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF | MTX_RECURSE); /* get interface index */ id = usbd_get_interface_descriptor(uaa->iface); if (id == NULL) { device_printf(dev, "failed to get " "interface number\n"); goto detach; } sc->sc_iface_no = id->bInterfaceNumber; #ifdef USB_DEBUG device_printf(dev, " "); switch (sc->sc_proto & UMASS_PROTO_COMMAND) { case UMASS_PROTO_SCSI: printf("SCSI"); break; case UMASS_PROTO_ATAPI: printf("8070i (ATAPI)"); break; case UMASS_PROTO_UFI: printf("UFI"); break; case UMASS_PROTO_RBC: printf("RBC"); break; default: printf("(unknown 0x%02x)", sc->sc_proto & UMASS_PROTO_COMMAND); break; } printf(" over "); switch (sc->sc_proto & UMASS_PROTO_WIRE) { case UMASS_PROTO_BBB: printf("Bulk-Only"); break; case UMASS_PROTO_CBI: /* uses Comand/Bulk pipes */ printf("CBI"); break; case UMASS_PROTO_CBI_I: /* uses Comand/Bulk/Interrupt pipes */ printf("CBI with CCI"); break; default: printf("(unknown 0x%02x)", sc->sc_proto & UMASS_PROTO_WIRE); } printf("; quirks = 0x%04x\n", sc->sc_quirks); #endif if (sc->sc_quirks & ALT_IFACE_1) { err = usbd_set_alt_interface_index (uaa->device, uaa->info.bIfaceIndex, 1); if (err) { DPRINTF(sc, UDMASS_USB, "could not switch to " "Alt Interface 1\n"); goto detach; } } /* allocate all required USB transfers */ if (sc->sc_proto & UMASS_PROTO_BBB) { err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, umass_bbb_config, UMASS_T_BBB_MAX, sc, &sc->sc_mtx); /* skip reset first time */ sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; } else if (sc->sc_proto & (UMASS_PROTO_CBI | UMASS_PROTO_CBI_I)) { err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, umass_cbi_config, UMASS_T_CBI_MAX, sc, &sc->sc_mtx); /* skip reset first time */ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; } else { err = USB_ERR_INVAL; } if (err) { device_printf(dev, "could not setup required " "transfers, %s\n", usbd_errstr(err)); goto detach; } #ifdef USB_DEBUG if (umass_throttle > 0) { uint8_t x; int iv; iv = umass_throttle; if (iv < 1) iv = 1; else if (iv > 8000) iv = 8000; for (x = 0; x != UMASS_T_MAX; x++) { if (sc->sc_xfer[x] != NULL) usbd_xfer_set_interval(sc->sc_xfer[x], iv); } } #endif sc->sc_transform = (sc->sc_proto & UMASS_PROTO_SCSI) ? &umass_scsi_transform : (sc->sc_proto & UMASS_PROTO_UFI) ? &umass_ufi_transform : (sc->sc_proto & UMASS_PROTO_ATAPI) ? &umass_atapi_transform : (sc->sc_proto & UMASS_PROTO_RBC) ? &umass_rbc_transform : &umass_no_transform; /* from here onwards the device can be used. */ if (sc->sc_quirks & SHUTTLE_INIT) { umass_init_shuttle(sc); } /* get the maximum LUN supported by the device */ if (((sc->sc_proto & UMASS_PROTO_WIRE) == UMASS_PROTO_BBB) && !(sc->sc_quirks & NO_GETMAXLUN)) sc->sc_maxlun = umass_bbb_get_max_lun(sc); else sc->sc_maxlun = 0; /* Prepare the SCSI command block */ sc->cam_scsi_sense.opcode = REQUEST_SENSE; sc->cam_scsi_test_unit_ready.opcode = TEST_UNIT_READY; /* register the SIM */ err = umass_cam_attach_sim(sc); if (err) { goto detach; } /* scan the SIM */ umass_cam_attach(sc); DPRINTF(sc, UDMASS_GEN, "Attach finished\n"); return (0); /* success */ detach: umass_detach(dev); return (ENXIO); /* failure */ } static int umass_detach(device_t dev) { struct umass_softc *sc = device_get_softc(dev); DPRINTF(sc, UDMASS_USB, "\n"); /* teardown our statemachine */ usbd_transfer_unsetup(sc->sc_xfer, UMASS_T_MAX); mtx_lock(&sc->sc_mtx); /* cancel any leftover CCB's */ umass_cancel_ccb(sc); umass_cam_detach_sim(sc); mtx_unlock(&sc->sc_mtx); mtx_destroy(&sc->sc_mtx); return (0); /* success */ } static void umass_init_shuttle(struct umass_softc *sc) { struct usb_device_request req; uint8_t status[2] = {0, 0}; /* * The Linux driver does this, but no one can tell us what the * command does. */ req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = 1; /* XXX unknown command */ USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, sizeof(status)); usbd_do_request(sc->sc_udev, NULL, &req, &status); DPRINTF(sc, UDMASS_GEN, "Shuttle init returned 0x%02x%02x\n", status[0], status[1]); } /* * Generic functions to handle transfers */ static void umass_transfer_start(struct umass_softc *sc, uint8_t xfer_index) { DPRINTF(sc, UDMASS_GEN, "transfer index = " "%d\n", xfer_index); if (sc->sc_xfer[xfer_index]) { sc->sc_last_xfer_index = xfer_index; usbd_transfer_start(sc->sc_xfer[xfer_index]); } else { umass_cancel_ccb(sc); } } static void umass_reset(struct umass_softc *sc) { DPRINTF(sc, UDMASS_GEN, "resetting device\n"); /* * stop the last transfer, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]); umass_transfer_start(sc, 0); } static void umass_cancel_ccb(struct umass_softc *sc) { union ccb *ccb; USB_MTX_ASSERT(&sc->sc_mtx, MA_OWNED); ccb = sc->sc_transfer.ccb; sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = 0; if (ccb) { (sc->sc_transfer.callback) (sc, ccb, (sc->sc_transfer.data_len - sc->sc_transfer.actlen), STATUS_WIRE_FAILED); } } static void umass_tr_error(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); if (error != USB_ERR_CANCELLED) { DPRINTF(sc, UDMASS_GEN, "transfer error, %s -> " "reset\n", usbd_errstr(error)); } umass_cancel_ccb(sc); } /* * BBB protocol specific functions */ static void umass_t_bbb_reset1_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: umass_transfer_start(sc, UMASS_T_BBB_RESET2); return; case USB_ST_SETUP: /* * Reset recovery (5.3.4 in Universal Serial Bus Mass Storage Class) * * For Reset Recovery the host shall issue in the following order: * a) a Bulk-Only Mass Storage Reset * b) a Clear Feature HALT to the Bulk-In endpoint * c) a Clear Feature HALT to the Bulk-Out endpoint * * This is done in 3 steps, using 3 transfers: * UMASS_T_BBB_RESET1 * UMASS_T_BBB_RESET2 * UMASS_T_BBB_RESET3 */ DPRINTF(sc, UDMASS_BBB, "BBB reset!\n"); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_BBB_RESET; /* bulk only reset */ USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); return; default: /* Error */ umass_tr_error(xfer, error); return; } } static void umass_t_bbb_reset2_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_RESET3, UMASS_T_BBB_DATA_READ, error); } static void umass_t_bbb_reset3_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_COMMAND, UMASS_T_BBB_DATA_WRITE, error); } static void umass_t_bbb_data_clear_stall_callback(struct usb_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: umass_transfer_start(sc, next_xfer); return; case USB_ST_SETUP: if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { goto tr_transferred; } return; default: /* Error */ umass_tr_error(xfer, error); return; } } static void umass_t_bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); union ccb *ccb = sc->sc_transfer.ccb; struct usb_page_cache *pc; uint32_t tag; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: umass_transfer_start (sc, ((sc->sc_transfer.dir == DIR_IN) ? UMASS_T_BBB_DATA_READ : (sc->sc_transfer.dir == DIR_OUT) ? UMASS_T_BBB_DATA_WRITE : UMASS_T_BBB_STATUS)); return; case USB_ST_SETUP: sc->sc_status_try = 0; if (ccb) { /* * the initial value is not important, * as long as the values are unique: */ tag = UGETDW(sc->cbw.dCBWTag) + 1; USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); USETDW(sc->cbw.dCBWTag, tag); /* * dCBWDataTransferLength: * This field indicates the number of bytes of data that the host * intends to transfer on the IN or OUT Bulk endpoint(as indicated by * the Direction bit) during the execution of this command. If this * field is set to 0, the device will expect that no data will be * transferred IN or OUT during this command, regardless of the value * of the Direction bit defined in dCBWFlags. */ USETDW(sc->cbw.dCBWDataTransferLength, sc->sc_transfer.data_len); /* * dCBWFlags: * The bits of the Flags field are defined as follows: * Bits 0-6 reserved * Bit 7 Direction - this bit shall be ignored if the * dCBWDataTransferLength field is zero. * 0 = data Out from host to device * 1 = data In from device to host */ sc->cbw.bCBWFlags = ((sc->sc_transfer.dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT); sc->cbw.bCBWLUN = sc->sc_transfer.lun; if (sc->sc_transfer.cmd_len > sizeof(sc->cbw.CBWCDB)) { sc->sc_transfer.cmd_len = sizeof(sc->cbw.CBWCDB); DPRINTF(sc, UDMASS_BBB, "Truncating long command!\n"); } sc->cbw.bCDBLength = sc->sc_transfer.cmd_len; /* copy SCSI command data */ memcpy(sc->cbw.CBWCDB, sc->sc_transfer.cmd_data, sc->sc_transfer.cmd_len); /* clear remaining command area */ memset(sc->cbw.CBWCDB + sc->sc_transfer.cmd_len, 0, sizeof(sc->cbw.CBWCDB) - sc->sc_transfer.cmd_len); DIF(UDMASS_BBB, umass_bbb_dump_cbw(sc, &sc->cbw)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &sc->cbw, sizeof(sc->cbw)); usbd_xfer_set_frame_len(xfer, 0, sizeof(sc->cbw)); usbd_transfer_submit(xfer); } return; default: /* Error */ umass_tr_error(xfer, error); return; } } static void umass_t_bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->sc_transfer.data_rem = 0; } case USB_ST_SETUP: DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", max_bulk, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_rem == 0) { umass_transfer_start(sc, UMASS_T_BBB_STATUS); return; } if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, max_bulk); usbd_transfer_submit(xfer); return; default: /* Error */ if (error == USB_ERR_CANCELLED) { umass_tr_error(xfer, error); } else { umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); } return; } } static void umass_t_bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, UMASS_T_BBB_DATA_READ, error); } static void umass_t_bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->sc_transfer.data_rem = 0; } case USB_ST_SETUP: DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", max_bulk, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_rem == 0) { umass_transfer_start(sc, UMASS_T_BBB_STATUS); return; } if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, max_bulk); usbd_transfer_submit(xfer); return; default: /* Error */ if (error == USB_ERR_CANCELLED) { umass_tr_error(xfer, error); } else { umass_transfer_start(sc, UMASS_T_BBB_DATA_WR_CS); } return; } } static void umass_t_bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, UMASS_T_BBB_DATA_WRITE, error); } static void umass_t_bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); union ccb *ccb = sc->sc_transfer.ccb; struct usb_page_cache *pc; uint32_t residue; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* * Do a full reset if there is something wrong with the CSW: */ sc->sc_status_try = 1; /* Zero missing parts of the CSW: */ if (actlen < (int)sizeof(sc->csw)) memset(&sc->csw, 0, sizeof(sc->csw)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &sc->csw, actlen); DIF(UDMASS_BBB, umass_bbb_dump_csw(sc, &sc->csw)); residue = UGETDW(sc->csw.dCSWDataResidue); if ((!residue) || (sc->sc_quirks & IGNORE_RESIDUE)) { residue = (sc->sc_transfer.data_len - sc->sc_transfer.actlen); } if (residue > sc->sc_transfer.data_len) { DPRINTF(sc, UDMASS_BBB, "truncating residue from %d " "to %d bytes\n", residue, sc->sc_transfer.data_len); residue = sc->sc_transfer.data_len; } /* translate weird command-status signatures: */ if (sc->sc_quirks & WRONG_CSWSIG) { uint32_t temp = UGETDW(sc->csw.dCSWSignature); if ((temp == CSWSIGNATURE_OLYMPUS_C1) || (temp == CSWSIGNATURE_IMAGINATION_DBX1)) { USETDW(sc->csw.dCSWSignature, CSWSIGNATURE); } } /* check CSW and handle eventual error */ if (UGETDW(sc->csw.dCSWSignature) != CSWSIGNATURE) { DPRINTF(sc, UDMASS_BBB, "bad CSW signature 0x%08x != 0x%08x\n", UGETDW(sc->csw.dCSWSignature), CSWSIGNATURE); /* * Invalid CSW: Wrong signature or wrong tag might * indicate that we lost synchronization. Reset the * device. */ goto tr_error; } else if (UGETDW(sc->csw.dCSWTag) != UGETDW(sc->cbw.dCBWTag)) { DPRINTF(sc, UDMASS_BBB, "Invalid CSW: tag 0x%08x should be " "0x%08x\n", UGETDW(sc->csw.dCSWTag), UGETDW(sc->cbw.dCBWTag)); goto tr_error; } else if (sc->csw.bCSWStatus > CSWSTATUS_PHASE) { DPRINTF(sc, UDMASS_BBB, "Invalid CSW: status %d > %d\n", sc->csw.bCSWStatus, CSWSTATUS_PHASE); goto tr_error; } else if (sc->csw.bCSWStatus == CSWSTATUS_PHASE) { DPRINTF(sc, UDMASS_BBB, "Phase error, residue = " "%d\n", residue); goto tr_error; } else if (sc->sc_transfer.actlen > sc->sc_transfer.data_len) { DPRINTF(sc, UDMASS_BBB, "Buffer overrun %d > %d\n", sc->sc_transfer.actlen, sc->sc_transfer.data_len); goto tr_error; } else if (sc->csw.bCSWStatus == CSWSTATUS_FAILED) { DPRINTF(sc, UDMASS_BBB, "Command failed, residue = " "%d\n", residue); sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; (sc->sc_transfer.callback) (sc, ccb, residue, STATUS_CMD_FAILED); } else { sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; (sc->sc_transfer.callback) (sc, ccb, residue, STATUS_CMD_OK); } return; case USB_ST_SETUP: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); return; default: tr_error: DPRINTF(sc, UDMASS_BBB, "Failed to read CSW: %s, try %d\n", usbd_errstr(error), sc->sc_status_try); if ((error == USB_ERR_CANCELLED) || (sc->sc_status_try)) { umass_tr_error(xfer, error); } else { sc->sc_status_try = 1; umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); } return; } } static void umass_command_start(struct umass_softc *sc, uint8_t dir, void *data_ptr, uint32_t data_len, uint32_t data_timeout, umass_callback_t *callback, union ccb *ccb) { sc->sc_transfer.lun = ccb->ccb_h.target_lun; /* * NOTE: assumes that "sc->sc_transfer.cmd_data" and * "sc->sc_transfer.cmd_len" has been properly * initialized. */ sc->sc_transfer.dir = data_len ? dir : DIR_NONE; sc->sc_transfer.data_ptr = data_ptr; sc->sc_transfer.data_len = data_len; sc->sc_transfer.data_rem = data_len; sc->sc_transfer.data_timeout = (data_timeout + UMASS_TIMEOUT); sc->sc_transfer.actlen = 0; sc->sc_transfer.callback = callback; sc->sc_transfer.ccb = ccb; if (sc->sc_xfer[sc->sc_last_xfer_index]) { usbd_transfer_start(sc->sc_xfer[sc->sc_last_xfer_index]); } else { umass_cancel_ccb(sc); } } static uint8_t umass_bbb_get_max_lun(struct umass_softc *sc) { struct usb_device_request req; usb_error_t err; uint8_t buf = 0; /* The Get Max Lun command is a class-specific request. */ req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UR_BBB_GET_MAX_LUN; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 1); err = usbd_do_request(sc->sc_udev, NULL, &req, &buf); if (err) { buf = 0; /* Device doesn't support Get Max Lun request. */ printf("%s: Get Max Lun not supported (%s)\n", sc->sc_name, usbd_errstr(err)); } return (buf); } /* * Command/Bulk/Interrupt (CBI) specific functions */ static void umass_cbi_start_status(struct umass_softc *sc) { if (sc->sc_xfer[UMASS_T_CBI_STATUS]) { umass_transfer_start(sc, UMASS_T_CBI_STATUS); } else { union ccb *ccb = sc->sc_transfer.ccb; sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; (sc->sc_transfer.callback) (sc, ccb, (sc->sc_transfer.data_len - sc->sc_transfer.actlen), STATUS_CMD_UNKNOWN); } } static void umass_t_cbi_reset1_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); struct usb_device_request req; struct usb_page_cache *pc; uint8_t buf[UMASS_CBI_DIAGNOSTIC_CMDLEN]; uint8_t i; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: umass_transfer_start(sc, UMASS_T_CBI_RESET2); break; case USB_ST_SETUP: /* * Command Block Reset Protocol * * First send a reset request to the device. Then clear * any possibly stalled bulk endpoints. * * This is done in 3 steps, using 3 transfers: * UMASS_T_CBI_RESET1 * UMASS_T_CBI_RESET2 * UMASS_T_CBI_RESET3 * UMASS_T_CBI_RESET4 (only if there is an interrupt endpoint) */ DPRINTF(sc, UDMASS_CBI, "CBI reset!\n"); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_CBI_ADSC; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; USETW(req.wLength, UMASS_CBI_DIAGNOSTIC_CMDLEN); /* * The 0x1d code is the SEND DIAGNOSTIC command. To * distinguish between the two, the last 10 bytes of the CBL * is filled with 0xff (section 2.2 of the CBI * specification) */ buf[0] = 0x1d; /* Command Block Reset */ buf[1] = 0x04; for (i = 2; i < UMASS_CBI_DIAGNOSTIC_CMDLEN; i++) { buf[i] = 0xff; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, buf, sizeof(buf)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, sizeof(buf)); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) umass_tr_error(xfer, error); else umass_transfer_start(sc, UMASS_T_CBI_RESET2); break; } } static void umass_t_cbi_reset2_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_RESET3, UMASS_T_CBI_DATA_READ, error); } static void umass_t_cbi_reset3_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); umass_t_cbi_data_clear_stall_callback (xfer, (sc->sc_xfer[UMASS_T_CBI_RESET4] && sc->sc_xfer[UMASS_T_CBI_STATUS]) ? UMASS_T_CBI_RESET4 : UMASS_T_CBI_COMMAND, UMASS_T_CBI_DATA_WRITE, error); } static void umass_t_cbi_reset4_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_COMMAND, UMASS_T_CBI_STATUS, error); } static void umass_t_cbi_data_clear_stall_callback(struct usb_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: if (next_xfer == UMASS_T_CBI_STATUS) { umass_cbi_start_status(sc); } else { umass_transfer_start(sc, next_xfer); } break; case USB_ST_SETUP: if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { goto tr_transferred; /* should not happen */ } break; default: /* Error */ umass_tr_error(xfer, error); break; } } static void umass_t_cbi_command_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); union ccb *ccb = sc->sc_transfer.ccb; struct usb_device_request req; struct usb_page_cache *pc; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (sc->sc_transfer.dir == DIR_NONE) { umass_cbi_start_status(sc); } else { umass_transfer_start (sc, (sc->sc_transfer.dir == DIR_IN) ? UMASS_T_CBI_DATA_READ : UMASS_T_CBI_DATA_WRITE); } break; case USB_ST_SETUP: if (ccb) { /* * do a CBI transfer with cmd_len bytes from * cmd_data, possibly a data phase of data_len * bytes from/to the device and finally a status * read phase. */ req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_CBI_ADSC; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_iface_no; req.wIndex[1] = 0; req.wLength[0] = sc->sc_transfer.cmd_len; req.wLength[1] = 0; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, sc->sc_transfer.cmd_data, sc->sc_transfer.cmd_len); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, sc->sc_transfer.cmd_len); usbd_xfer_set_frames(xfer, sc->sc_transfer.cmd_len ? 2 : 1); DIF(UDMASS_CBI, umass_cbi_dump_cmd(sc, sc->sc_transfer.cmd_data, sc->sc_transfer.cmd_len)); usbd_transfer_submit(xfer); } break; default: /* Error */ /* * STALL on the control pipe can be result of the command error. * Attempt to clear this STALL same as for bulk pipe also * results in command completion interrupt, but ASC/ASCQ there * look like not always valid, so don't bother about it. */ if ((error == USB_ERR_STALLED) || (sc->sc_transfer.callback == &umass_cam_cb)) { sc->sc_transfer.ccb = NULL; (sc->sc_transfer.callback) (sc, ccb, sc->sc_transfer.data_len, STATUS_CMD_UNKNOWN); } else { umass_tr_error(xfer, error); /* skip reset */ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; } break; } } static void umass_t_cbi_data_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->sc_transfer.data_rem = 0; } case USB_ST_SETUP: DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", max_bulk, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_rem == 0) { umass_cbi_start_status(sc); break; } if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if ((error == USB_ERR_CANCELLED) || (sc->sc_transfer.callback != &umass_cam_cb)) { umass_tr_error(xfer, error); } else { umass_transfer_start(sc, UMASS_T_CBI_DATA_RD_CS); } break; } } static void umass_t_cbi_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, UMASS_T_CBI_DATA_READ, error); } static void umass_t_cbi_data_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); uint32_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->sc_transfer.data_rem -= actlen; sc->sc_transfer.data_ptr += actlen; sc->sc_transfer.actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->sc_transfer.data_rem = 0; } case USB_ST_SETUP: DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", max_bulk, sc->sc_transfer.data_rem); if (sc->sc_transfer.data_rem == 0) { umass_cbi_start_status(sc); break; } if (max_bulk > sc->sc_transfer.data_rem) { max_bulk = sc->sc_transfer.data_rem; } usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if ((error == USB_ERR_CANCELLED) || (sc->sc_transfer.callback != &umass_cam_cb)) { umass_tr_error(xfer, error); } else { umass_transfer_start(sc, UMASS_T_CBI_DATA_WR_CS); } break; } } static void umass_t_cbi_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) { umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, UMASS_T_CBI_DATA_WRITE, error); } static void umass_t_cbi_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct umass_softc *sc = usbd_xfer_softc(xfer); union ccb *ccb = sc->sc_transfer.ccb; struct usb_page_cache *pc; uint32_t residue; uint8_t status; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < (int)sizeof(sc->sbl)) { goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &sc->sbl, sizeof(sc->sbl)); residue = (sc->sc_transfer.data_len - sc->sc_transfer.actlen); /* dissect the information in the buffer */ if (sc->sc_proto & UMASS_PROTO_UFI) { /* * Section 3.4.3.1.3 specifies that the UFI command * protocol returns an ASC and ASCQ in the interrupt * data block. */ DPRINTF(sc, UDMASS_CBI, "UFI CCI, ASC = 0x%02x, " "ASCQ = 0x%02x\n", sc->sbl.ufi.asc, sc->sbl.ufi.ascq); status = (((sc->sbl.ufi.asc == 0) && (sc->sbl.ufi.ascq == 0)) ? STATUS_CMD_OK : STATUS_CMD_FAILED); sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; (sc->sc_transfer.callback) (sc, ccb, residue, status); break; } else { /* Command Interrupt Data Block */ DPRINTF(sc, UDMASS_CBI, "type=0x%02x, value=0x%02x\n", sc->sbl.common.type, sc->sbl.common.value); if (sc->sbl.common.type == IDB_TYPE_CCI) { status = (sc->sbl.common.value & IDB_VALUE_STATUS_MASK); status = ((status == IDB_VALUE_PASS) ? STATUS_CMD_OK : (status == IDB_VALUE_FAIL) ? STATUS_CMD_FAILED : (status == IDB_VALUE_PERSISTENT) ? STATUS_CMD_FAILED : STATUS_WIRE_FAILED); sc->sc_transfer.ccb = NULL; sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; (sc->sc_transfer.callback) (sc, ccb, residue, status); break; } } /* fallthrough */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF(sc, UDMASS_CBI, "Failed to read CSW: %s\n", usbd_errstr(error)); umass_tr_error(xfer, error); break; } } /* * CAM specific functions (used by SCSI, UFI, 8070i (ATAPI)) */ static int umass_cam_attach_sim(struct umass_softc *sc) { struct cam_devq *devq; /* Per device Queue */ cam_status status; /* * A HBA is attached to the CAM layer. * * The CAM layer will then after a while start probing for devices on * the bus. The number of SIMs is limited to one. */ devq = cam_simq_alloc(1 /* maximum openings */ ); if (devq == NULL) { return (ENOMEM); } sc->sc_sim = cam_sim_alloc (&umass_cam_action, &umass_cam_poll, DEVNAME_SIM, sc /* priv */ , sc->sc_unit /* unit number */ , &sc->sc_mtx /* mutex */ , 1 /* maximum device openings */ , 0 /* maximum tagged device openings */ , devq); if (sc->sc_sim == NULL) { cam_simq_free(devq); return (ENOMEM); } mtx_lock(&sc->sc_mtx); status = xpt_bus_register(sc->sc_sim, sc->sc_dev, sc->sc_unit); if (status != CAM_SUCCESS) { cam_sim_free(sc->sc_sim, /* free_devq */ TRUE); mtx_unlock(&sc->sc_mtx); printf("%s: xpt_bus_register failed with status %#x\n", __func__, status); return (ENOMEM); } mtx_unlock(&sc->sc_mtx); return (0); } static void umass_cam_attach(struct umass_softc *sc) { #ifndef USB_DEBUG if (bootverbose) #endif printf("%s:%d:%d: Attached to scbus%d\n", sc->sc_name, cam_sim_path(sc->sc_sim), sc->sc_unit, cam_sim_path(sc->sc_sim)); } /* umass_cam_detach * detach from the CAM layer */ static void umass_cam_detach_sim(struct umass_softc *sc) { cam_status status; if (sc->sc_sim != NULL) { status = xpt_bus_deregister(cam_sim_path(sc->sc_sim)); if (status == CAM_REQ_CMP) { /* accessing the softc is not possible after this */ sc->sc_sim->softc = NULL; DPRINTF(sc, UDMASS_SCSI, "%s: %s:%d:%d caling " "cam_sim_free sim %p refc %u mtx %p\n", __func__, sc->sc_name, cam_sim_path(sc->sc_sim), sc->sc_unit, sc->sc_sim, sc->sc_sim->refcount, sc->sc_sim->mtx); cam_sim_free(sc->sc_sim, /* free_devq */ TRUE); } else { panic("%s: %s: CAM layer is busy: %#x\n", __func__, sc->sc_name, status); } sc->sc_sim = NULL; } } /* umass_cam_action * CAM requests for action come through here */ static void umass_cam_action(struct cam_sim *sim, union ccb *ccb) { struct umass_softc *sc = (struct umass_softc *)sim->softc; if (sc == NULL) { ccb->ccb_h.status = CAM_SEL_TIMEOUT; xpt_done(ccb); return; } /* Perform the requested action */ switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: { uint8_t *cmd; uint8_t dir; if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); } else { cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); } DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SCSI_IO: " "cmd: 0x%02x, flags: 0x%02x, " "%db cmd/%db data/%db sense\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun, cmd[0], ccb->ccb_h.flags & CAM_DIR_MASK, ccb->csio.cdb_len, ccb->csio.dxfer_len, ccb->csio.sense_len); if (sc->sc_transfer.ccb) { DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SCSI_IO: " "I/O in progress, deferring\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); ccb->ccb_h.status = CAM_SCSI_BUSY; xpt_done(ccb); goto done; } switch (ccb->ccb_h.flags & CAM_DIR_MASK) { case CAM_DIR_IN: dir = DIR_IN; break; case CAM_DIR_OUT: dir = DIR_OUT; DIF(UDMASS_SCSI, umass_dump_buffer(sc, ccb->csio.data_ptr, ccb->csio.dxfer_len, 48)); break; default: dir = DIR_NONE; } ccb->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED; /* * sc->sc_transform will convert the command to the * command format needed by the specific command set * and return the converted command in * "sc->sc_transfer.cmd_data" */ if (umass_std_transform(sc, ccb, cmd, ccb->csio.cdb_len)) { if (sc->sc_transfer.cmd_data[0] == INQUIRY) { const char *pserial; pserial = usb_get_serial(sc->sc_udev); /* * Umass devices don't generally report their serial numbers * in the usual SCSI way. Emulate it here. */ if ((sc->sc_transfer.cmd_data[1] & SI_EVPD) && (sc->sc_transfer.cmd_data[2] == SVPD_UNIT_SERIAL_NUMBER) && (pserial[0] != '\0')) { struct scsi_vpd_unit_serial_number *vpd_serial; vpd_serial = (struct scsi_vpd_unit_serial_number *)ccb->csio.data_ptr; vpd_serial->length = strlen(pserial); if (vpd_serial->length > sizeof(vpd_serial->serial_num)) vpd_serial->length = sizeof(vpd_serial->serial_num); memcpy(vpd_serial->serial_num, pserial, vpd_serial->length); ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); goto done; } /* * Handle EVPD inquiry for broken devices first * NO_INQUIRY also implies NO_INQUIRY_EVPD */ if ((sc->sc_quirks & (NO_INQUIRY_EVPD | NO_INQUIRY)) && (sc->sc_transfer.cmd_data[1] & SI_EVPD)) { scsi_set_sense_data(&ccb->csio.sense_data, /*sense_format*/ SSD_TYPE_NONE, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x24, /*ascq*/ 0x00, /*extra args*/ SSD_ELEM_NONE); ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID | CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, 1); xpt_done(ccb); goto done; } /* * Return fake inquiry data for * broken devices */ if (sc->sc_quirks & NO_INQUIRY) { memcpy(ccb->csio.data_ptr, &fake_inq_data, sizeof(fake_inq_data)); ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); goto done; } if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { ccb->csio.dxfer_len = SHORT_INQUIRY_LENGTH; } } else if (sc->sc_transfer.cmd_data[0] == PREVENT_ALLOW) { if (sc->sc_quirks & NO_PREVENT_ALLOW) { ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); goto done; } } else if (sc->sc_transfer.cmd_data[0] == SYNCHRONIZE_CACHE) { if (sc->sc_quirks & NO_SYNCHRONIZE_CACHE) { ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); goto done; } + } else if (sc->sc_transfer.cmd_data[0] == START_STOP_UNIT) { + if (sc->sc_quirks & NO_START_STOP) { + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } } umass_command_start(sc, dir, ccb->csio.data_ptr, ccb->csio.dxfer_len, ccb->ccb_h.timeout, &umass_cam_cb, ccb); } break; } case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_PATH_INQ:.\n", sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); /* host specific information */ cpi->version_num = 1; cpi->hba_inquiry = 0; cpi->target_sprt = 0; cpi->hba_misc = PIM_NO_6_BYTE; cpi->hba_eng_cnt = 0; cpi->max_target = UMASS_SCSIID_MAX; /* one target */ cpi->initiator_id = UMASS_SCSIID_HOST; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "USB SCSI", HBA_IDLEN); strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = sc->sc_unit; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->transport = XPORT_USB; cpi->transport_version = 0; if (sc == NULL) { cpi->base_transfer_speed = 0; cpi->max_lun = 0; } else { if (sc->sc_quirks & FLOPPY_SPEED) { cpi->base_transfer_speed = UMASS_FLOPPY_TRANSFER_SPEED; } else { switch (usbd_get_speed(sc->sc_udev)) { case USB_SPEED_SUPER: cpi->base_transfer_speed = UMASS_SUPER_TRANSFER_SPEED; cpi->maxio = maxphys; break; case USB_SPEED_HIGH: cpi->base_transfer_speed = UMASS_HIGH_TRANSFER_SPEED; break; default: cpi->base_transfer_speed = UMASS_FULL_TRANSFER_SPEED; break; } } cpi->max_lun = sc->sc_maxlun; } cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_RESET_DEV: { DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_RESET_DEV:.\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); umass_reset(sc); ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_GET_TRAN_SETTINGS:.\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_USB; cts->transport_version = 0; cts->xport_specific.valid = 0; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_SET_TRAN_SETTINGS: { DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SET_TRAN_SETTINGS:.\n", cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; xpt_done(ccb); break; } case XPT_CALC_GEOMETRY: { cam_calc_geometry(&ccb->ccg, /* extended */ 1); xpt_done(ccb); break; } case XPT_NOOP: { DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_NOOP:.\n", sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } default: DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:func_code 0x%04x: " "Not implemented\n", sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun, ccb->ccb_h.func_code); ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; xpt_done(ccb); break; } done: return; } static void umass_cam_poll(struct cam_sim *sim) { struct umass_softc *sc = (struct umass_softc *)sim->softc; if (sc == NULL) return; DPRINTF(sc, UDMASS_SCSI, "CAM poll\n"); usbd_transfer_poll(sc->sc_xfer, UMASS_T_MAX); } /* umass_cam_cb * finalise a completed CAM command */ static void umass_cam_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status) { ccb->csio.resid = residue; switch (status) { case STATUS_CMD_OK: ccb->ccb_h.status = CAM_REQ_CMP; if ((sc->sc_quirks & READ_CAPACITY_OFFBY1) && (ccb->ccb_h.func_code == XPT_SCSI_IO) && (ccb->csio.cdb_io.cdb_bytes[0] == READ_CAPACITY)) { struct scsi_read_capacity_data *rcap; uint32_t maxsector; rcap = (void *)(ccb->csio.data_ptr); maxsector = scsi_4btoul(rcap->addr) - 1; scsi_ulto4b(maxsector, rcap->addr); } /* * We have to add SVPD_UNIT_SERIAL_NUMBER to the list * of pages supported by the device - otherwise, CAM * will never ask us for the serial number if the * device cannot handle that by itself. */ if (ccb->ccb_h.func_code == XPT_SCSI_IO && sc->sc_transfer.cmd_data[0] == INQUIRY && (sc->sc_transfer.cmd_data[1] & SI_EVPD) && sc->sc_transfer.cmd_data[2] == SVPD_SUPPORTED_PAGE_LIST && (usb_get_serial(sc->sc_udev)[0] != '\0')) { struct ccb_scsiio *csio; struct scsi_vpd_supported_page_list *page_list; csio = &ccb->csio; page_list = (struct scsi_vpd_supported_page_list *)csio->data_ptr; if (page_list->length + 1 < SVPD_SUPPORTED_PAGES_SIZE) { page_list->list[page_list->length] = SVPD_UNIT_SERIAL_NUMBER; page_list->length++; } } xpt_done(ccb); break; case STATUS_CMD_UNKNOWN: case STATUS_CMD_FAILED: /* fetch sense data */ /* the rest of the command was filled in at attach */ sc->cam_scsi_sense.length = ccb->csio.sense_len; DPRINTF(sc, UDMASS_SCSI, "Fetching %d bytes of " "sense data\n", ccb->csio.sense_len); if (umass_std_transform(sc, ccb, &sc->cam_scsi_sense.opcode, sizeof(sc->cam_scsi_sense))) { if ((sc->sc_quirks & FORCE_SHORT_INQUIRY) && (sc->sc_transfer.cmd_data[0] == INQUIRY)) { ccb->csio.sense_len = SHORT_INQUIRY_LENGTH; } umass_command_start(sc, DIR_IN, &ccb->csio.sense_data.error_code, ccb->csio.sense_len, ccb->ccb_h.timeout, &umass_cam_sense_cb, ccb); } break; default: /* * The wire protocol failed and will hopefully have * recovered. We return an error to CAM and let CAM * retry the command if necessary. */ xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN; xpt_done(ccb); break; } } /* * Finalise a completed autosense operation */ static void umass_cam_sense_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status) { uint8_t *cmd; switch (status) { case STATUS_CMD_OK: case STATUS_CMD_UNKNOWN: case STATUS_CMD_FAILED: { int key, sense_len; ccb->csio.sense_resid = residue; sense_len = ccb->csio.sense_len - ccb->csio.sense_resid; key = scsi_get_sense_key(&ccb->csio.sense_data, sense_len, /*show_errors*/ 1); if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); } else { cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); } /* * Getting sense data always succeeds (apart from wire * failures): */ if ((sc->sc_quirks & RS_NO_CLEAR_UA) && (cmd[0] == INQUIRY) && (key == SSD_KEY_UNIT_ATTENTION)) { /* * Ignore unit attention errors in the case where * the Unit Attention state is not cleared on * REQUEST SENSE. They will appear again at the next * command. */ ccb->ccb_h.status = CAM_REQ_CMP; } else if (key == SSD_KEY_NO_SENSE) { /* * No problem after all (in the case of CBI without * CCI) */ ccb->ccb_h.status = CAM_REQ_CMP; } else if ((sc->sc_quirks & RS_NO_CLEAR_UA) && (cmd[0] == READ_CAPACITY) && (key == SSD_KEY_UNIT_ATTENTION)) { /* * Some devices do not clear the unit attention error * on request sense. We insert a test unit ready * command to make sure we clear the unit attention * condition, then allow the retry to proceed as * usual. */ xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID | CAM_DEV_QFRZN; ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; #if 0 DELAY(300000); #endif DPRINTF(sc, UDMASS_SCSI, "Doing a sneaky" "TEST_UNIT_READY\n"); /* the rest of the command was filled in at attach */ if ((sc->sc_transform)(sc, &sc->cam_scsi_test_unit_ready.opcode, sizeof(sc->cam_scsi_test_unit_ready)) == 1) { umass_command_start(sc, DIR_NONE, NULL, 0, ccb->ccb_h.timeout, &umass_cam_quirk_cb, ccb); break; } } else { xpt_freeze_devq(ccb->ccb_h.path, 1); if (key >= 0) { ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID | CAM_DEV_QFRZN; ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; } else ccb->ccb_h.status = CAM_AUTOSENSE_FAIL | CAM_DEV_QFRZN; } xpt_done(ccb); break; } default: DPRINTF(sc, UDMASS_SCSI, "Autosense failed, " "status %d\n", status); xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status = CAM_AUTOSENSE_FAIL | CAM_DEV_QFRZN; xpt_done(ccb); } } /* * This completion code just handles the fact that we sent a test-unit-ready * after having previously failed a READ CAPACITY with CHECK_COND. The CCB * status for CAM is already set earlier. */ static void umass_cam_quirk_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status) { DPRINTF(sc, UDMASS_SCSI, "Test unit ready " "returned status %d\n", status); xpt_done(ccb); } /* * SCSI specific functions */ static uint8_t umass_scsi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) { if ((cmd_len == 0) || (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { DPRINTF(sc, UDMASS_SCSI, "Invalid command " "length: %d bytes\n", cmd_len); return (0); /* failure */ } sc->sc_transfer.cmd_len = cmd_len; switch (cmd_ptr[0]) { case TEST_UNIT_READY: if (sc->sc_quirks & NO_TEST_UNIT_READY) { DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " "to START_UNIT\n"); memset(sc->sc_transfer.cmd_data, 0, cmd_len); sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; sc->sc_transfer.cmd_data[4] = SSS_START; return (1); } break; case INQUIRY: /* * some drives wedge when asked for full inquiry * information. */ if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; return (1); } break; } memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); return (1); } static uint8_t umass_rbc_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) { if ((cmd_len == 0) || (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { DPRINTF(sc, UDMASS_SCSI, "Invalid command " "length: %d bytes\n", cmd_len); return (0); /* failure */ } switch (cmd_ptr[0]) { /* these commands are defined in RBC: */ case READ_10: case READ_CAPACITY: case START_STOP_UNIT: case SYNCHRONIZE_CACHE: case WRITE_10: case VERIFY_10: case INQUIRY: case MODE_SELECT_10: case MODE_SENSE_10: case TEST_UNIT_READY: case WRITE_BUFFER: /* * The following commands are not listed in my copy of the * RBC specs. CAM however seems to want those, and at least * the Sony DSC device appears to support those as well */ case REQUEST_SENSE: case PREVENT_ALLOW: memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); if ((sc->sc_quirks & RBC_PAD_TO_12) && (cmd_len < 12)) { memset(sc->sc_transfer.cmd_data + cmd_len, 0, 12 - cmd_len); cmd_len = 12; } sc->sc_transfer.cmd_len = cmd_len; return (1); /* success */ /* All other commands are not legal in RBC */ default: DPRINTF(sc, UDMASS_SCSI, "Unsupported RBC " "command 0x%02x\n", cmd_ptr[0]); return (0); /* failure */ } } static uint8_t umass_ufi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) { if ((cmd_len == 0) || (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { DPRINTF(sc, UDMASS_SCSI, "Invalid command " "length: %d bytes\n", cmd_len); return (0); /* failure */ } /* An UFI command is always 12 bytes in length */ sc->sc_transfer.cmd_len = UFI_COMMAND_LENGTH; /* Zero the command data */ memset(sc->sc_transfer.cmd_data, 0, UFI_COMMAND_LENGTH); switch (cmd_ptr[0]) { /* * Commands of which the format has been verified. They * should work. Copy the command into the (zeroed out) * destination buffer. */ case TEST_UNIT_READY: if (sc->sc_quirks & NO_TEST_UNIT_READY) { /* * Some devices do not support this command. Start * Stop Unit should give the same results */ DPRINTF(sc, UDMASS_UFI, "Converted TEST_UNIT_READY " "to START_UNIT\n"); sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; sc->sc_transfer.cmd_data[4] = SSS_START; return (1); } break; case REZERO_UNIT: case REQUEST_SENSE: case FORMAT_UNIT: case INQUIRY: case START_STOP_UNIT: case SEND_DIAGNOSTIC: case PREVENT_ALLOW: case READ_CAPACITY: case READ_10: case WRITE_10: case POSITION_TO_ELEMENT: /* SEEK_10 */ case WRITE_AND_VERIFY: case VERIFY: case MODE_SELECT_10: case MODE_SENSE_10: case READ_12: case WRITE_12: case READ_FORMAT_CAPACITIES: break; /* * SYNCHRONIZE_CACHE isn't supported by UFI, nor should it be * required for UFI devices, so it is appropriate to fake * success. */ case SYNCHRONIZE_CACHE: return (2); default: DPRINTF(sc, UDMASS_SCSI, "Unsupported UFI " "command 0x%02x\n", cmd_ptr[0]); return (0); /* failure */ } memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); return (1); /* success */ } /* * 8070i (ATAPI) specific functions */ static uint8_t umass_atapi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) { if ((cmd_len == 0) || (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { DPRINTF(sc, UDMASS_SCSI, "Invalid command " "length: %d bytes\n", cmd_len); return (0); /* failure */ } /* An ATAPI command is always 12 bytes in length. */ sc->sc_transfer.cmd_len = ATAPI_COMMAND_LENGTH; /* Zero the command data */ memset(sc->sc_transfer.cmd_data, 0, ATAPI_COMMAND_LENGTH); switch (cmd_ptr[0]) { /* * Commands of which the format has been verified. They * should work. Copy the command into the destination * buffer. */ case INQUIRY: /* * some drives wedge when asked for full inquiry * information. */ if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; return (1); } break; case TEST_UNIT_READY: if (sc->sc_quirks & NO_TEST_UNIT_READY) { DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " "to START_UNIT\n"); sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; sc->sc_transfer.cmd_data[4] = SSS_START; return (1); } break; case REZERO_UNIT: case REQUEST_SENSE: case START_STOP_UNIT: case SEND_DIAGNOSTIC: case PREVENT_ALLOW: case READ_CAPACITY: case READ_10: case WRITE_10: case POSITION_TO_ELEMENT: /* SEEK_10 */ case SYNCHRONIZE_CACHE: case MODE_SELECT_10: case MODE_SENSE_10: case READ_BUFFER: case 0x42: /* READ_SUBCHANNEL */ case 0x43: /* READ_TOC */ case 0x44: /* READ_HEADER */ case 0x47: /* PLAY_MSF (Play Minute/Second/Frame) */ case 0x48: /* PLAY_TRACK */ case 0x49: /* PLAY_TRACK_REL */ case 0x4b: /* PAUSE */ case 0x51: /* READ_DISK_INFO */ case 0x52: /* READ_TRACK_INFO */ case 0x54: /* SEND_OPC */ case 0x59: /* READ_MASTER_CUE */ case 0x5b: /* CLOSE_TR_SESSION */ case 0x5c: /* READ_BUFFER_CAP */ case 0x5d: /* SEND_CUE_SHEET */ case 0xa1: /* BLANK */ case 0xa5: /* PLAY_12 */ case 0xa6: /* EXCHANGE_MEDIUM */ case 0xad: /* READ_DVD_STRUCTURE */ case 0xbb: /* SET_CD_SPEED */ case 0xe5: /* READ_TRACK_INFO_PHILIPS */ break; case READ_12: case WRITE_12: default: DPRINTF(sc, UDMASS_SCSI, "Unsupported ATAPI " "command 0x%02x - trying anyway\n", cmd_ptr[0]); break; } memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); return (1); /* success */ } static uint8_t umass_no_transform(struct umass_softc *sc, uint8_t *cmd, uint8_t cmdlen) { return (0); /* failure */ } static uint8_t umass_std_transform(struct umass_softc *sc, union ccb *ccb, uint8_t *cmd, uint8_t cmdlen) { uint8_t retval; retval = (sc->sc_transform) (sc, cmd, cmdlen); if (retval == 2) { ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); return (0); } else if (retval == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status = CAM_REQ_INVALID | CAM_DEV_QFRZN; xpt_done(ccb); return (0); } /* Command should be executed */ return (1); } #ifdef USB_DEBUG static void umass_bbb_dump_cbw(struct umass_softc *sc, umass_bbb_cbw_t *cbw) { uint8_t *c = cbw->CBWCDB; uint32_t dlen = UGETDW(cbw->dCBWDataTransferLength); uint32_t tag = UGETDW(cbw->dCBWTag); uint8_t clen = cbw->bCDBLength; uint8_t flags = cbw->bCBWFlags; uint8_t lun = cbw->bCBWLUN; DPRINTF(sc, UDMASS_BBB, "CBW %d: cmd = %db " "(0x%02x%02x%02x%02x%02x%02x%s), " "data = %db, lun = %d, dir = %s\n", tag, clen, c[0], c[1], c[2], c[3], c[4], c[5], (clen > 6 ? "..." : ""), dlen, lun, (flags == CBWFLAGS_IN ? "in" : (flags == CBWFLAGS_OUT ? "out" : ""))); } static void umass_bbb_dump_csw(struct umass_softc *sc, umass_bbb_csw_t *csw) { uint32_t sig = UGETDW(csw->dCSWSignature); uint32_t tag = UGETDW(csw->dCSWTag); uint32_t res = UGETDW(csw->dCSWDataResidue); uint8_t status = csw->bCSWStatus; DPRINTF(sc, UDMASS_BBB, "CSW %d: sig = 0x%08x (%s), tag = 0x%08x, " "res = %d, status = 0x%02x (%s)\n", tag, sig, (sig == CSWSIGNATURE ? "valid" : "invalid"), tag, res, status, (status == CSWSTATUS_GOOD ? "good" : (status == CSWSTATUS_FAILED ? "failed" : (status == CSWSTATUS_PHASE ? "phase" : "")))); } static void umass_cbi_dump_cmd(struct umass_softc *sc, void *cmd, uint8_t cmdlen) { uint8_t *c = cmd; uint8_t dir = sc->sc_transfer.dir; DPRINTF(sc, UDMASS_BBB, "cmd = %db " "(0x%02x%02x%02x%02x%02x%02x%s), " "data = %db, dir = %s\n", cmdlen, c[0], c[1], c[2], c[3], c[4], c[5], (cmdlen > 6 ? "..." : ""), sc->sc_transfer.data_len, (dir == DIR_IN ? "in" : (dir == DIR_OUT ? "out" : (dir == DIR_NONE ? "no data phase" : "")))); } static void umass_dump_buffer(struct umass_softc *sc, uint8_t *buffer, uint32_t buflen, uint32_t printlen) { uint32_t i, j; char s1[40]; char s2[40]; char s3[5]; s1[0] = '\0'; s3[0] = '\0'; sprintf(s2, " buffer=%p, buflen=%d", buffer, buflen); for (i = 0; (i < buflen) && (i < printlen); i++) { j = i % 16; if (j == 0 && i != 0) { DPRINTF(sc, UDMASS_GEN, "0x %s%s\n", s1, s2); s2[0] = '\0'; } sprintf(&s1[j * 2], "%02x", buffer[i] & 0xff); } if (buflen > printlen) sprintf(s3, " ..."); DPRINTF(sc, UDMASS_GEN, "0x %s%s%s\n", s1, s2, s3); } #endif diff --git a/sys/dev/usb/usb_device.c b/sys/dev/usb/usb_device.c index 322e4f5401ba..8d0e7961f675 100644 --- a/sys/dev/usb/usb_device.c +++ b/sys/dev/usb/usb_device.c @@ -1,3092 +1,3095 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008-2020 Hans Petter Selasky. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if USB_HAVE_UGEN #include #endif #include "usbdevs.h" #define USB_DEBUG_VAR usb_debug #include #include #include #include #include #include #include #include #include #include #include #if USB_HAVE_UGEN #include #include #endif #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ /* function prototypes */ static int sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS); static void usb_init_endpoint(struct usb_device *, uint8_t, struct usb_endpoint_descriptor *, struct usb_endpoint_ss_comp_descriptor *, struct usb_endpoint *); static void usb_unconfigure(struct usb_device *, uint8_t); static void usb_detach_device_sub(struct usb_device *, device_t *, char **, uint8_t); static uint8_t usb_probe_and_attach_sub(struct usb_device *, struct usb_attach_arg *); static void usb_init_attach_arg(struct usb_device *, struct usb_attach_arg *); static void usb_suspend_resume_sub(struct usb_device *, device_t, uint8_t); static usb_proc_callback_t usbd_clear_stall_proc; static usb_error_t usb_config_parse(struct usb_device *, uint8_t, uint8_t); #if USB_HAVE_DEVCTL static void usb_notify_addq(const char *type, struct usb_device *); #endif #if USB_HAVE_UGEN static void usb_fifo_free_wrap(struct usb_device *, uint8_t, uint8_t); static void usb_cdev_create(struct usb_device *); static void usb_cdev_free(struct usb_device *); #endif /* This variable is global to allow easy access to it: */ #ifdef USB_TEMPLATE int usb_template = USB_TEMPLATE; #else int usb_template = -1; #endif SYSCTL_PROC(_hw_usb, OID_AUTO, template, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, NULL, 0, sysctl_hw_usb_template, "I", "Selected USB device side template"); /*------------------------------------------------------------------------* * usb_trigger_reprobe_on_off * * This function sets the pull up resistors for all ports currently * operating in device mode either on (when on_not_off is 1), or off * (when it's 0). *------------------------------------------------------------------------*/ static void usb_trigger_reprobe_on_off(int on_not_off) { struct usb_port_status ps; struct usb_bus *bus; struct usb_device *udev; usb_error_t err; int do_unlock, max; max = devclass_get_maxunit(usb_devclass_ptr); while (max >= 0) { mtx_lock(&usb_ref_lock); bus = devclass_get_softc(usb_devclass_ptr, max); max--; if (bus == NULL || bus->devices == NULL || bus->devices[USB_ROOT_HUB_ADDR] == NULL) { mtx_unlock(&usb_ref_lock); continue; } udev = bus->devices[USB_ROOT_HUB_ADDR]; if (udev->refcount == USB_DEV_REF_MAX) { mtx_unlock(&usb_ref_lock); continue; } udev->refcount++; mtx_unlock(&usb_ref_lock); do_unlock = usbd_enum_lock(udev); if (do_unlock > 1) { do_unlock = 0; goto next; } err = usbd_req_get_port_status(udev, NULL, &ps, 1); if (err != 0) { DPRINTF("usbd_req_get_port_status() " "failed: %s\n", usbd_errstr(err)); goto next; } if ((UGETW(ps.wPortStatus) & UPS_PORT_MODE_DEVICE) == 0) goto next; if (on_not_off) { err = usbd_req_set_port_feature(udev, NULL, 1, UHF_PORT_POWER); if (err != 0) { DPRINTF("usbd_req_set_port_feature() " "failed: %s\n", usbd_errstr(err)); } } else { err = usbd_req_clear_port_feature(udev, NULL, 1, UHF_PORT_POWER); if (err != 0) { DPRINTF("usbd_req_clear_port_feature() " "failed: %s\n", usbd_errstr(err)); } } next: mtx_lock(&usb_ref_lock); if (do_unlock) usbd_enum_unlock(udev); if (--(udev->refcount) == 0) cv_broadcast(&udev->ref_cv); mtx_unlock(&usb_ref_lock); } } /*------------------------------------------------------------------------* * usb_trigger_reprobe_all * * This function toggles the pull up resistors for all ports currently * operating in device mode, causing the host machine to reenumerate them. *------------------------------------------------------------------------*/ static void usb_trigger_reprobe_all(void) { /* * Set the pull up resistors off for all ports in device mode. */ usb_trigger_reprobe_on_off(0); /* * According to the DWC OTG spec this must be at least 3ms. */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME)); /* * Set the pull up resistors back on. */ usb_trigger_reprobe_on_off(1); } static int sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS) { int error, val; val = usb_template; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL || usb_template == val) return (error); usb_template = val; if (usb_template < 0) { usb_trigger_reprobe_on_off(0); } else { usb_trigger_reprobe_all(); } return (0); } /* English is default language */ static int usb_lang_id = 0x0009; static int usb_lang_mask = 0x00FF; SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_id, CTLFLAG_RWTUN, &usb_lang_id, 0, "Preferred USB language ID"); SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_mask, CTLFLAG_RWTUN, &usb_lang_mask, 0, "Preferred USB language mask"); static const char* statestr[USB_STATE_MAX] = { [USB_STATE_DETACHED] = "DETACHED", [USB_STATE_ATTACHED] = "ATTACHED", [USB_STATE_POWERED] = "POWERED", [USB_STATE_ADDRESSED] = "ADDRESSED", [USB_STATE_CONFIGURED] = "CONFIGURED", }; const char * usb_statestr(enum usb_dev_state state) { return ((state < USB_STATE_MAX) ? statestr[state] : "UNKNOWN"); } const char * usb_get_manufacturer(struct usb_device *udev) { return (udev->manufacturer ? udev->manufacturer : "Unknown"); } const char * usb_get_product(struct usb_device *udev) { return (udev->product ? udev->product : ""); } const char * usb_get_serial(struct usb_device *udev) { return (udev->serial ? udev->serial : ""); } /*------------------------------------------------------------------------* * usbd_get_ep_by_addr * * This function searches for an USB ep by endpoint address and * direction. * * Returns: * NULL: Failure * Else: Success *------------------------------------------------------------------------*/ struct usb_endpoint * usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val) { struct usb_endpoint *ep = udev->endpoints; struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max; enum { EA_MASK = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR), }; /* * According to the USB specification not all bits are used * for the endpoint address. Keep defined bits only: */ ea_val &= EA_MASK; /* * Iterate across all the USB endpoints searching for a match * based on the endpoint address: */ for (; ep != ep_end; ep++) { if (ep->edesc == NULL) { continue; } /* do the mask and check the value */ if ((ep->edesc->bEndpointAddress & EA_MASK) == ea_val) { goto found; } } /* * The default endpoint is always present and is checked separately: */ if ((udev->ctrl_ep.edesc != NULL) && ((udev->ctrl_ep.edesc->bEndpointAddress & EA_MASK) == ea_val)) { ep = &udev->ctrl_ep; goto found; } return (NULL); found: return (ep); } /*------------------------------------------------------------------------* * usbd_get_endpoint * * This function searches for an USB endpoint based on the information * given by the passed "struct usb_config" pointer. * * Return values: * NULL: No match. * Else: Pointer to "struct usb_endpoint". *------------------------------------------------------------------------*/ struct usb_endpoint * usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index, const struct usb_config *setup) { struct usb_endpoint *ep = udev->endpoints; struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max; uint8_t index = setup->ep_index; uint8_t ea_mask; uint8_t ea_val; uint8_t type_mask; uint8_t type_val; DPRINTFN(10, "udev=%p iface_index=%d address=0x%x " "type=0x%x dir=0x%x index=%d\n", udev, iface_index, setup->endpoint, setup->type, setup->direction, setup->ep_index); /* check USB mode */ if (setup->usb_mode != USB_MODE_DUAL && udev->flags.usb_mode != setup->usb_mode) { /* wrong mode - no endpoint */ return (NULL); } /* setup expected endpoint direction mask and value */ if (setup->direction == UE_DIR_RX) { ea_mask = (UE_DIR_IN | UE_DIR_OUT); ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ? UE_DIR_OUT : UE_DIR_IN; } else if (setup->direction == UE_DIR_TX) { ea_mask = (UE_DIR_IN | UE_DIR_OUT); ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ? UE_DIR_IN : UE_DIR_OUT; } else if (setup->direction == UE_DIR_ANY) { /* match any endpoint direction */ ea_mask = 0; ea_val = 0; } else { /* match the given endpoint direction */ ea_mask = (UE_DIR_IN | UE_DIR_OUT); ea_val = (setup->direction & (UE_DIR_IN | UE_DIR_OUT)); } /* setup expected endpoint address */ if (setup->endpoint == UE_ADDR_ANY) { /* match any endpoint address */ } else { /* match the given endpoint address */ ea_mask |= UE_ADDR; ea_val |= (setup->endpoint & UE_ADDR); } /* setup expected endpoint type */ if (setup->type == UE_BULK_INTR) { /* this will match BULK and INTERRUPT endpoints */ type_mask = 2; type_val = 2; } else if (setup->type == UE_TYPE_ANY) { /* match any endpoint type */ type_mask = 0; type_val = 0; } else { /* match the given endpoint type */ type_mask = UE_XFERTYPE; type_val = (setup->type & UE_XFERTYPE); } /* * Iterate across all the USB endpoints searching for a match * based on the endpoint address. Note that we are searching * the endpoints from the beginning of the "udev->endpoints" array. */ for (; ep != ep_end; ep++) { if ((ep->edesc == NULL) || (ep->iface_index != iface_index)) { continue; } /* do the masks and check the values */ if (((ep->edesc->bEndpointAddress & ea_mask) == ea_val) && ((ep->edesc->bmAttributes & type_mask) == type_val)) { if (!index--) { goto found; } } } /* * Match against default endpoint last, so that "any endpoint", "any * address" and "any direction" returns the first endpoint of the * interface. "iface_index" and "direction" is ignored: */ if ((udev->ctrl_ep.edesc != NULL) && ((udev->ctrl_ep.edesc->bEndpointAddress & ea_mask) == ea_val) && ((udev->ctrl_ep.edesc->bmAttributes & type_mask) == type_val) && (!index)) { ep = &udev->ctrl_ep; goto found; } return (NULL); found: return (ep); } /*------------------------------------------------------------------------* * usbd_interface_count * * This function stores the number of USB interfaces excluding * alternate settings, which the USB config descriptor reports into * the unsigned 8-bit integer pointed to by "count". * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usbd_interface_count(struct usb_device *udev, uint8_t *count) { if (udev->cdesc == NULL) { *count = 0; return (USB_ERR_NOT_CONFIGURED); } *count = udev->ifaces_max; return (USB_ERR_NORMAL_COMPLETION); } /*------------------------------------------------------------------------* * usb_init_endpoint * * This function will initialise the USB endpoint structure pointed to by * the "endpoint" argument. The structure pointed to by "endpoint" must be * zeroed before calling this function. *------------------------------------------------------------------------*/ static void usb_init_endpoint(struct usb_device *udev, uint8_t iface_index, struct usb_endpoint_descriptor *edesc, struct usb_endpoint_ss_comp_descriptor *ecomp, struct usb_endpoint *ep) { const struct usb_bus_methods *methods; usb_stream_t x; methods = udev->bus->methods; (methods->endpoint_init) (udev, edesc, ep); /* initialise USB endpoint structure */ ep->edesc = edesc; ep->ecomp = ecomp; ep->iface_index = iface_index; /* setup USB stream queues */ for (x = 0; x != USB_MAX_EP_STREAMS; x++) { TAILQ_INIT(&ep->endpoint_q[x].head); ep->endpoint_q[x].command = &usbd_pipe_start; } /* the pipe is not supported by the hardware */ if (ep->methods == NULL) return; /* check for SUPER-speed streams mode endpoint */ if (udev->speed == USB_SPEED_SUPER && ecomp != NULL && (edesc->bmAttributes & UE_XFERTYPE) == UE_BULK && (UE_GET_BULK_STREAMS(ecomp->bmAttributes) != 0)) { usbd_set_endpoint_mode(udev, ep, USB_EP_MODE_STREAMS); } else { usbd_set_endpoint_mode(udev, ep, USB_EP_MODE_DEFAULT); } /* clear stall, if any */ if (methods->clear_stall != NULL) { USB_BUS_LOCK(udev->bus); (methods->clear_stall) (udev, ep); USB_BUS_UNLOCK(udev->bus); } } /*-----------------------------------------------------------------------* * usb_endpoint_foreach * * This function will iterate all the USB endpoints except the control * endpoint. This function is NULL safe. * * Return values: * NULL: End of USB endpoints * Else: Pointer to next USB endpoint *------------------------------------------------------------------------*/ struct usb_endpoint * usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep) { struct usb_endpoint *ep_end; /* be NULL safe */ if (udev == NULL) return (NULL); ep_end = udev->endpoints + udev->endpoints_max; /* get next endpoint */ if (ep == NULL) ep = udev->endpoints; else ep++; /* find next allocated ep */ while (ep != ep_end) { if (ep->edesc != NULL) return (ep); ep++; } return (NULL); } /*------------------------------------------------------------------------* * usb_wait_pending_refs * * This function will wait for any USB references to go away before * returning. This function is used before freeing a USB device. *------------------------------------------------------------------------*/ static void usb_wait_pending_refs(struct usb_device *udev) { #if USB_HAVE_UGEN DPRINTF("Refcount = %d\n", (int)udev->refcount); mtx_lock(&usb_ref_lock); udev->refcount--; while (1) { /* wait for any pending references to go away */ if (udev->refcount == 0) { /* prevent further refs being taken, if any */ udev->refcount = USB_DEV_REF_MAX; break; } cv_wait(&udev->ref_cv, &usb_ref_lock); } mtx_unlock(&usb_ref_lock); #endif } /*------------------------------------------------------------------------* * usb_unconfigure * * This function will free all USB interfaces and USB endpoints belonging * to an USB device. * * Flag values, see "USB_UNCFG_FLAG_XXX". *------------------------------------------------------------------------*/ static void usb_unconfigure(struct usb_device *udev, uint8_t flag) { uint8_t do_unlock; /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); /* detach all interface drivers */ usb_detach_device(udev, USB_IFACE_INDEX_ANY, flag); #if USB_HAVE_UGEN /* free all FIFOs except control endpoint FIFOs */ usb_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, flag); /* * Free all cdev's, if any. */ usb_cdev_free(udev); #endif #if USB_HAVE_COMPAT_LINUX /* free Linux compat device, if any */ if (udev->linux_endpoint_start != NULL) { usb_linux_free_device_p(udev); udev->linux_endpoint_start = NULL; } #endif usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_FREE); /* free "cdesc" after "ifaces" and "endpoints", if any */ if (udev->cdesc != NULL) { if (udev->flags.usb_mode != USB_MODE_DEVICE) usbd_free_config_desc(udev, udev->cdesc); udev->cdesc = NULL; } /* set unconfigured state */ udev->curr_config_no = USB_UNCONFIG_NO; udev->curr_config_index = USB_UNCONFIG_INDEX; if (do_unlock) usbd_enum_unlock(udev); } /*------------------------------------------------------------------------* * usbd_set_config_index * * This function selects configuration by index, independent of the * actual configuration number. This function should not be used by * USB drivers. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usbd_set_config_index(struct usb_device *udev, uint8_t index) { struct usb_status ds; struct usb_config_descriptor *cdp; uint16_t power; uint16_t max_power; uint8_t selfpowered; uint8_t do_unlock; usb_error_t err; DPRINTFN(6, "udev=%p index=%d\n", udev, index); /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); usb_unconfigure(udev, 0); if (index == USB_UNCONFIG_INDEX) { /* * Leave unallocated when unconfiguring the * device. "usb_unconfigure()" will also reset * the current config number and index. */ err = usbd_req_set_config(udev, NULL, USB_UNCONFIG_NO); if (udev->state == USB_STATE_CONFIGURED) usb_set_device_state(udev, USB_STATE_ADDRESSED); goto done; } /* get the full config descriptor */ if (udev->flags.usb_mode == USB_MODE_DEVICE) { /* save some memory */ err = usbd_req_get_descriptor_ptr(udev, &cdp, (UDESC_CONFIG << 8) | index); } else { /* normal request */ err = usbd_req_get_config_desc_full(udev, NULL, &cdp, index); } if (err) { goto done; } /* set the new config descriptor */ udev->cdesc = cdp; /* Figure out if the device is self or bus powered. */ selfpowered = 0; if ((!udev->flags.uq_bus_powered) && (cdp->bmAttributes & UC_SELF_POWERED) && (udev->flags.usb_mode == USB_MODE_HOST)) { /* May be self powered. */ if (cdp->bmAttributes & UC_BUS_POWERED) { /* Must ask device. */ err = usbd_req_get_device_status(udev, NULL, &ds); if (err) { DPRINTFN(0, "could not read " "device status: %s\n", usbd_errstr(err)); } else if (UGETW(ds.wStatus) & UDS_SELF_POWERED) { selfpowered = 1; } DPRINTF("status=0x%04x \n", UGETW(ds.wStatus)); } else selfpowered = 1; } DPRINTF("udev=%p cdesc=%p (addr %d) cno=%d attr=0x%02x, " "selfpowered=%d, power=%d\n", udev, cdp, udev->address, cdp->bConfigurationValue, cdp->bmAttributes, selfpowered, cdp->bMaxPower * 2); /* Check if we have enough power. */ power = cdp->bMaxPower * 2; if (udev->parent_hub) { max_power = udev->parent_hub->hub->portpower; } else { max_power = USB_MAX_POWER; } if (power > max_power) { DPRINTFN(0, "power exceeded %d > %d\n", power, max_power); err = USB_ERR_NO_POWER; goto done; } /* Only update "self_powered" in USB Host Mode */ if (udev->flags.usb_mode == USB_MODE_HOST) { udev->flags.self_powered = selfpowered; } udev->power = power; udev->curr_config_no = cdp->bConfigurationValue; udev->curr_config_index = index; usb_set_device_state(udev, USB_STATE_CONFIGURED); /* Set the actual configuration value. */ err = usbd_req_set_config(udev, NULL, cdp->bConfigurationValue); if (err) { goto done; } err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_ALLOC); if (err) { goto done; } err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_INIT); if (err) { goto done; } #if USB_HAVE_UGEN /* create device nodes for each endpoint */ usb_cdev_create(udev); #endif done: DPRINTF("error=%s\n", usbd_errstr(err)); if (err) { usb_unconfigure(udev, 0); } if (do_unlock) usbd_enum_unlock(udev); return (err); } /*------------------------------------------------------------------------* * usb_config_parse * * This function will allocate and free USB interfaces and USB endpoints, * parse the USB configuration structure and initialise the USB endpoints * and interfaces. If "iface_index" is not equal to * "USB_IFACE_INDEX_ANY" then the "cmd" parameter is the * alternate_setting to be selected for the given interface. Else the * "cmd" parameter is defined by "USB_CFG_XXX". "iface_index" can be * "USB_IFACE_INDEX_ANY" or a valid USB interface index. This function * is typically called when setting the configuration or when setting * an alternate interface. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static usb_error_t usb_config_parse(struct usb_device *udev, uint8_t iface_index, uint8_t cmd) { struct usb_idesc_parse_state ips; struct usb_interface_descriptor *id; struct usb_endpoint_descriptor *ed; struct usb_interface *iface; struct usb_endpoint *ep; usb_error_t err; uint8_t ep_curr; uint8_t ep_max; uint8_t temp; uint8_t do_init; uint8_t alt_index; if (iface_index != USB_IFACE_INDEX_ANY) { /* parameter overload */ alt_index = cmd; cmd = USB_CFG_INIT; } else { /* not used */ alt_index = 0; } err = 0; DPRINTFN(5, "iface_index=%d cmd=%d\n", iface_index, cmd); if (cmd == USB_CFG_FREE) goto cleanup; if (cmd == USB_CFG_INIT) { sx_assert(&udev->enum_sx, SA_LOCKED); /* check for in-use endpoints */ ep = udev->endpoints; ep_max = udev->endpoints_max; while (ep_max--) { /* look for matching endpoints */ if ((iface_index == USB_IFACE_INDEX_ANY) || (iface_index == ep->iface_index)) { if (ep->refcount_alloc != 0) { /* * This typically indicates a * more serious error. */ err = USB_ERR_IN_USE; } else { /* reset endpoint */ memset(ep, 0, sizeof(*ep)); /* make sure we don't zero the endpoint again */ ep->iface_index = USB_IFACE_INDEX_ANY; } } ep++; } if (err) return (err); } memset(&ips, 0, sizeof(ips)); ep_curr = 0; ep_max = 0; while ((id = usb_idesc_foreach(udev->cdesc, &ips))) { iface = udev->ifaces + ips.iface_index; /* check for specific interface match */ if (cmd == USB_CFG_INIT) { if ((iface_index != USB_IFACE_INDEX_ANY) && (iface_index != ips.iface_index)) { /* wrong interface */ do_init = 0; } else if (alt_index != ips.iface_index_alt) { /* wrong alternate setting */ do_init = 0; } else { /* initialise interface */ do_init = 1; } /* update number of alternate settings, if any */ if (iface_index == USB_IFACE_INDEX_ANY) iface->num_altsetting = ips.iface_index_alt + 1; } else do_init = 0; /* check for new interface */ if (ips.iface_index_alt == 0) { /* update current number of endpoints */ ep_curr = ep_max; } /* check for init */ if (do_init) { /* setup the USB interface structure */ iface->idesc = id; /* set alternate index */ iface->alt_index = alt_index; /* set default interface parent */ if (iface_index == USB_IFACE_INDEX_ANY) { iface->parent_iface_index = USB_IFACE_INDEX_ANY; } } DPRINTFN(5, "found idesc nendpt=%d\n", id->bNumEndpoints); ed = (struct usb_endpoint_descriptor *)id; temp = ep_curr; /* iterate all the endpoint descriptors */ while ((ed = usb_edesc_foreach(udev->cdesc, ed))) { /* check if endpoint limit has been reached */ if (temp >= USB_MAX_EP_UNITS) { DPRINTF("Endpoint limit reached\n"); break; } ep = udev->endpoints + temp; if (do_init) { void *ecomp; ecomp = usb_ed_comp_foreach(udev->cdesc, (void *)ed); if (ecomp != NULL) DPRINTFN(5, "Found endpoint companion descriptor\n"); usb_init_endpoint(udev, ips.iface_index, ed, ecomp, ep); } temp ++; /* find maximum number of endpoints */ if (ep_max < temp) ep_max = temp; } } /* NOTE: It is valid to have no interfaces and no endpoints! */ if (cmd == USB_CFG_ALLOC) { udev->ifaces_max = ips.iface_index; #if (USB_HAVE_FIXED_IFACE == 0) udev->ifaces = NULL; if (udev->ifaces_max != 0) { udev->ifaces = malloc(sizeof(*iface) * udev->ifaces_max, M_USB, M_WAITOK | M_ZERO); if (udev->ifaces == NULL) { err = USB_ERR_NOMEM; goto done; } } #endif #if (USB_HAVE_FIXED_ENDPOINT == 0) if (ep_max != 0) { udev->endpoints = malloc(sizeof(*ep) * ep_max, M_USB, M_WAITOK | M_ZERO); if (udev->endpoints == NULL) { err = USB_ERR_NOMEM; goto done; } } else { udev->endpoints = NULL; } #endif USB_BUS_LOCK(udev->bus); udev->endpoints_max = ep_max; /* reset any ongoing clear-stall */ udev->ep_curr = NULL; USB_BUS_UNLOCK(udev->bus); } #if (USB_HAVE_FIXED_IFACE == 0) || (USB_HAVE_FIXED_ENDPOINT == 0) done: #endif if (err) { if (cmd == USB_CFG_ALLOC) { cleanup: USB_BUS_LOCK(udev->bus); udev->endpoints_max = 0; /* reset any ongoing clear-stall */ udev->ep_curr = NULL; USB_BUS_UNLOCK(udev->bus); #if (USB_HAVE_FIXED_IFACE == 0) free(udev->ifaces, M_USB); udev->ifaces = NULL; #endif #if (USB_HAVE_FIXED_ENDPOINT == 0) free(udev->endpoints, M_USB); udev->endpoints = NULL; #endif udev->ifaces_max = 0; } } return (err); } /*------------------------------------------------------------------------* * usbd_set_alt_interface_index * * This function will select an alternate interface index for the * given interface index. The interface should not be in use when this * function is called. That means there should not be any open USB * transfers. Else an error is returned. If the alternate setting is * already set this function will simply return success. This function * is called in Host mode and Device mode! * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usbd_set_alt_interface_index(struct usb_device *udev, uint8_t iface_index, uint8_t alt_index) { struct usb_interface *iface = usbd_get_iface(udev, iface_index); usb_error_t err; uint8_t do_unlock; /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); if (iface == NULL) { err = USB_ERR_INVAL; goto done; } if (iface->alt_index == alt_index) { /* * Optimise away duplicate setting of * alternate setting in USB Host Mode! */ err = 0; goto done; } #if USB_HAVE_UGEN /* * Free all generic FIFOs for this interface, except control * endpoint FIFOs: */ usb_fifo_free_wrap(udev, iface_index, 0); #endif err = usb_config_parse(udev, iface_index, alt_index); if (err) { goto done; } if (iface->alt_index != alt_index) { /* the alternate setting does not exist */ err = USB_ERR_INVAL; goto done; } err = usbd_req_set_alt_interface_no(udev, NULL, iface_index, iface->idesc->bAlternateSetting); done: if (do_unlock) usbd_enum_unlock(udev); return (err); } /*------------------------------------------------------------------------* * usbd_set_endpoint_stall * * This function is used to make a BULK or INTERRUPT endpoint send * STALL tokens in USB device mode. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usbd_set_endpoint_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t do_stall) { struct usb_xfer *xfer; usb_stream_t x; uint8_t et; uint8_t was_stalled; if (ep == NULL) { /* nothing to do */ DPRINTF("Cannot find endpoint\n"); /* * Pretend that the clear or set stall request is * successful else some USB host stacks can do * strange things, especially when a control endpoint * stalls. */ return (0); } et = (ep->edesc->bmAttributes & UE_XFERTYPE); if ((et != UE_BULK) && (et != UE_INTERRUPT)) { /* * Should not stall control * nor isochronous endpoints. */ DPRINTF("Invalid endpoint\n"); return (0); } USB_BUS_LOCK(udev->bus); /* store current stall state */ was_stalled = ep->is_stalled; /* check for no change */ if (was_stalled && do_stall) { /* if the endpoint is already stalled do nothing */ USB_BUS_UNLOCK(udev->bus); DPRINTF("No change\n"); return (0); } /* set stalled state */ ep->is_stalled = 1; if (do_stall || (!was_stalled)) { if (!was_stalled) { for (x = 0; x != USB_MAX_EP_STREAMS; x++) { /* lookup the current USB transfer, if any */ xfer = ep->endpoint_q[x].curr; if (xfer != NULL) { /* * The "xfer_stall" method * will complete the USB * transfer like in case of a * timeout setting the error * code "USB_ERR_STALLED". */ (udev->bus->methods->xfer_stall) (xfer); } } } (udev->bus->methods->set_stall) (udev, ep, &do_stall); } if (!do_stall) { ep->toggle_next = 0; /* reset data toggle */ ep->is_stalled = 0; /* clear stalled state */ (udev->bus->methods->clear_stall) (udev, ep); /* start the current or next transfer, if any */ for (x = 0; x != USB_MAX_EP_STREAMS; x++) { usb_command_wrapper(&ep->endpoint_q[x], ep->endpoint_q[x].curr); } } USB_BUS_UNLOCK(udev->bus); return (0); } /*------------------------------------------------------------------------* * usb_reset_iface_endpoints - used in USB device side mode *------------------------------------------------------------------------*/ usb_error_t usb_reset_iface_endpoints(struct usb_device *udev, uint8_t iface_index) { struct usb_endpoint *ep; struct usb_endpoint *ep_end; ep = udev->endpoints; ep_end = udev->endpoints + udev->endpoints_max; for (; ep != ep_end; ep++) { if ((ep->edesc == NULL) || (ep->iface_index != iface_index)) { continue; } /* simulate a clear stall from the peer */ usbd_set_endpoint_stall(udev, ep, 0); } return (0); } /*------------------------------------------------------------------------* * usb_detach_device_sub * * This function will try to detach an USB device. If it fails a panic * will result. * * Flag values, see "USB_UNCFG_FLAG_XXX". *------------------------------------------------------------------------*/ static void usb_detach_device_sub(struct usb_device *udev, device_t *ppdev, char **ppnpinfo, uint8_t flag) { device_t dev; char *pnpinfo; int err; dev = *ppdev; if (dev) { /* * NOTE: It is important to clear "*ppdev" before deleting * the child due to some device methods being called late * during the delete process ! */ *ppdev = NULL; if (!rebooting) { device_printf(dev, "at %s, port %d, addr %d " "(disconnected)\n", device_get_nameunit(udev->parent_dev), udev->port_no, udev->address); } if (device_is_attached(dev)) { if (udev->flags.peer_suspended) { err = DEVICE_RESUME(dev); if (err) { device_printf(dev, "Resume failed\n"); } } } /* detach and delete child */ if (device_delete_child(udev->parent_dev, dev)) { goto error; } } pnpinfo = *ppnpinfo; if (pnpinfo != NULL) { *ppnpinfo = NULL; free(pnpinfo, M_USBDEV); } return; error: /* Detach is not allowed to fail in the USB world */ panic("usb_detach_device_sub: A USB driver would not detach\n"); } /*------------------------------------------------------------------------* * usb_detach_device * * The following function will detach the matching interfaces. * This function is NULL safe. * * Flag values, see "USB_UNCFG_FLAG_XXX". *------------------------------------------------------------------------*/ void usb_detach_device(struct usb_device *udev, uint8_t iface_index, uint8_t flag) { struct usb_interface *iface; uint8_t i; if (udev == NULL) { /* nothing to do */ return; } DPRINTFN(4, "udev=%p\n", udev); sx_assert(&udev->enum_sx, SA_LOCKED); /* * First detach the child to give the child's detach routine a * chance to detach the sub-devices in the correct order. * Then delete the child using "device_delete_child()" which * will detach all sub-devices from the bottom and upwards! */ if (iface_index != USB_IFACE_INDEX_ANY) { i = iface_index; iface_index = i + 1; } else { i = 0; iface_index = USB_IFACE_MAX; } /* do the detach */ for (; i != iface_index; i++) { iface = usbd_get_iface(udev, i); if (iface == NULL) { /* looks like the end of the USB interfaces */ break; } usb_detach_device_sub(udev, &iface->subdev, &iface->pnpinfo, flag); } } /*------------------------------------------------------------------------* * usb_probe_and_attach_sub * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t usb_probe_and_attach_sub(struct usb_device *udev, struct usb_attach_arg *uaa) { struct usb_interface *iface; device_t dev; int err; iface = uaa->iface; if (iface->parent_iface_index != USB_IFACE_INDEX_ANY) { /* leave interface alone */ return (0); } dev = iface->subdev; if (dev) { /* clean up after module unload */ if (device_is_attached(dev)) { /* already a device there */ return (0); } /* clear "iface->subdev" as early as possible */ iface->subdev = NULL; if (device_delete_child(udev->parent_dev, dev)) { /* * Panic here, else one can get a double call * to device_detach(). USB devices should * never fail on detach! */ panic("device_delete_child() failed\n"); } } if (uaa->temp_dev == NULL) { /* create a new child */ uaa->temp_dev = device_add_child(udev->parent_dev, NULL, -1); if (uaa->temp_dev == NULL) { device_printf(udev->parent_dev, "Device creation failed\n"); return (1); /* failure */ } device_set_ivars(uaa->temp_dev, uaa); device_quiet(uaa->temp_dev); } /* * Set "subdev" before probe and attach so that "devd" gets * the information it needs. */ iface->subdev = uaa->temp_dev; if (device_probe_and_attach(iface->subdev) == 0) { /* * The USB attach arguments are only available during probe * and attach ! */ uaa->temp_dev = NULL; device_set_ivars(iface->subdev, NULL); if (udev->flags.peer_suspended) { err = DEVICE_SUSPEND(iface->subdev); if (err) device_printf(iface->subdev, "Suspend failed\n"); } return (0); /* success */ } else { /* No USB driver found */ iface->subdev = NULL; } return (1); /* failure */ } /*------------------------------------------------------------------------* * usbd_set_parent_iface * * Using this function will lock the alternate interface setting on an * interface. It is typically used for multi interface drivers. In USB * device side mode it is assumed that the alternate interfaces all * have the same endpoint descriptors. The default parent index value * is "USB_IFACE_INDEX_ANY". Then the alternate setting value is not * locked. *------------------------------------------------------------------------*/ void usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index, uint8_t parent_index) { struct usb_interface *iface; if (udev == NULL || iface_index == parent_index) { /* nothing to do */ return; } iface = usbd_get_iface(udev, iface_index); if (iface != NULL) iface->parent_iface_index = parent_index; } static void usb_init_attach_arg(struct usb_device *udev, struct usb_attach_arg *uaa) { memset(uaa, 0, sizeof(*uaa)); uaa->device = udev; uaa->usb_mode = udev->flags.usb_mode; uaa->port = udev->port_no; uaa->dev_state = UAA_DEV_READY; uaa->info.idVendor = UGETW(udev->ddesc.idVendor); uaa->info.idProduct = UGETW(udev->ddesc.idProduct); uaa->info.bcdDevice = UGETW(udev->ddesc.bcdDevice); uaa->info.bDeviceClass = udev->ddesc.bDeviceClass; uaa->info.bDeviceSubClass = udev->ddesc.bDeviceSubClass; uaa->info.bDeviceProtocol = udev->ddesc.bDeviceProtocol; uaa->info.bConfigIndex = udev->curr_config_index; uaa->info.bConfigNum = udev->curr_config_no; } /*------------------------------------------------------------------------* * usb_probe_and_attach * * This function is called from "uhub_explore_sub()", * "usb_handle_set_config()" and "usb_handle_request()". * * Returns: * 0: Success * Else: A control transfer failed *------------------------------------------------------------------------*/ usb_error_t usb_probe_and_attach(struct usb_device *udev, uint8_t iface_index) { struct usb_attach_arg uaa; struct usb_interface *iface; uint8_t i; uint8_t j; uint8_t do_unlock; if (udev == NULL) { DPRINTF("udev == NULL\n"); return (USB_ERR_INVAL); } /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); if (udev->curr_config_index == USB_UNCONFIG_INDEX) { /* do nothing - no configuration has been set */ goto done; } /* setup USB attach arguments */ usb_init_attach_arg(udev, &uaa); /* * If the whole USB device is targeted, invoke the USB event * handler(s): */ if (iface_index == USB_IFACE_INDEX_ANY) { if (usb_test_quirk(&uaa, UQ_MSC_DYMO_EJECT) != 0 && usb_dymo_eject(udev, 0) == 0) { /* success, mark the udev as disappearing */ uaa.dev_state = UAA_DEV_EJECTING; } EVENTHANDLER_INVOKE(usb_dev_configured, udev, &uaa); if (uaa.dev_state != UAA_DEV_READY) { /* leave device unconfigured */ usb_unconfigure(udev, 0); goto done; } } /* Check if only one interface should be probed: */ if (iface_index != USB_IFACE_INDEX_ANY) { i = iface_index; j = i + 1; } else { i = 0; j = USB_IFACE_MAX; } /* Do the probe and attach */ for (; i != j; i++) { iface = usbd_get_iface(udev, i); if (iface == NULL) { /* * Looks like the end of the USB * interfaces ! */ DPRINTFN(2, "end of interfaces " "at %u\n", i); break; } if (iface->idesc == NULL) { /* no interface descriptor */ continue; } uaa.iface = iface; uaa.info.bInterfaceClass = iface->idesc->bInterfaceClass; uaa.info.bInterfaceSubClass = iface->idesc->bInterfaceSubClass; uaa.info.bInterfaceProtocol = iface->idesc->bInterfaceProtocol; uaa.info.bIfaceIndex = i; uaa.info.bIfaceNum = iface->idesc->bInterfaceNumber; uaa.driver_info = 0; /* reset driver_info */ DPRINTFN(2, "iclass=%u/%u/%u iindex=%u/%u\n", uaa.info.bInterfaceClass, uaa.info.bInterfaceSubClass, uaa.info.bInterfaceProtocol, uaa.info.bIfaceIndex, uaa.info.bIfaceNum); usb_probe_and_attach_sub(udev, &uaa); /* * Remove the leftover child, if any, to enforce that * a new nomatch devd event is generated for the next * interface if no driver is found: */ if (uaa.temp_dev == NULL) continue; if (device_delete_child(udev->parent_dev, uaa.temp_dev)) DPRINTFN(0, "device delete child failed\n"); uaa.temp_dev = NULL; } done: if (do_unlock) usbd_enum_unlock(udev); return (0); } /*------------------------------------------------------------------------* * usb_suspend_resume_sub * * This function is called when the suspend or resume methods should * be executed on an USB device. *------------------------------------------------------------------------*/ static void usb_suspend_resume_sub(struct usb_device *udev, device_t dev, uint8_t do_suspend) { int err; if (dev == NULL) { return; } if (!device_is_attached(dev)) { return; } if (do_suspend) { err = DEVICE_SUSPEND(dev); } else { err = DEVICE_RESUME(dev); } if (err) { device_printf(dev, "%s failed\n", do_suspend ? "Suspend" : "Resume"); } } /*------------------------------------------------------------------------* * usb_suspend_resume * * The following function will suspend or resume the USB device. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usb_suspend_resume(struct usb_device *udev, uint8_t do_suspend) { struct usb_interface *iface; uint8_t i; if (udev == NULL) { /* nothing to do */ return (0); } DPRINTFN(4, "udev=%p do_suspend=%d\n", udev, do_suspend); sx_assert(&udev->sr_sx, SA_LOCKED); USB_BUS_LOCK(udev->bus); /* filter the suspend events */ if (udev->flags.peer_suspended == do_suspend) { USB_BUS_UNLOCK(udev->bus); /* nothing to do */ return (0); } udev->flags.peer_suspended = do_suspend; USB_BUS_UNLOCK(udev->bus); /* do the suspend or resume */ for (i = 0; i != USB_IFACE_MAX; i++) { iface = usbd_get_iface(udev, i); if (iface == NULL) { /* looks like the end of the USB interfaces */ break; } usb_suspend_resume_sub(udev, iface->subdev, do_suspend); } return (0); } /*------------------------------------------------------------------------* * usbd_clear_stall_proc * * This function performs generic USB clear stall operations. *------------------------------------------------------------------------*/ static void usbd_clear_stall_proc(struct usb_proc_msg *_pm) { struct usb_udev_msg *pm = (void *)_pm; struct usb_device *udev = pm->udev; /* Change lock */ USB_BUS_UNLOCK(udev->bus); USB_MTX_LOCK(&udev->device_mtx); /* Start clear stall callback */ usbd_transfer_start(udev->ctrl_xfer[1]); /* Change lock */ USB_MTX_UNLOCK(&udev->device_mtx); USB_BUS_LOCK(udev->bus); } /*------------------------------------------------------------------------* * usb_get_langid * * This function tries to figure out the USB string language to use. *------------------------------------------------------------------------*/ void usb_get_langid(struct usb_device *udev) { uint8_t *scratch_ptr; uint8_t do_unlock; int err; /* * Workaround for buggy USB devices. * * It appears that some string-less USB chips will crash and * disappear if any attempts are made to read any string * descriptors. * * Try to detect such chips by checking the strings in the USB * device descriptor. If no strings are present there we * simply disable all USB strings. */ /* Protect scratch area */ do_unlock = usbd_ctrl_lock(udev); scratch_ptr = udev->scratch.data; if (udev->flags.no_strings) { err = USB_ERR_INVAL; } else if (udev->ddesc.iManufacturer || udev->ddesc.iProduct || udev->ddesc.iSerialNumber) { /* read out the language ID string */ err = usbd_req_get_string_desc(udev, NULL, (char *)scratch_ptr, 4, 0, USB_LANGUAGE_TABLE); } else { err = USB_ERR_INVAL; } if (err || (scratch_ptr[0] < 4)) { udev->flags.no_strings = 1; } else { uint16_t langid; uint16_t pref; uint16_t mask; uint8_t x; /* load preferred value and mask */ pref = usb_lang_id; mask = usb_lang_mask; /* align length correctly */ scratch_ptr[0] &= ~1U; /* fix compiler warning */ langid = 0; /* search for preferred language */ for (x = 2; x < scratch_ptr[0]; x += 2) { langid = UGETW(scratch_ptr + x); if ((langid & mask) == pref) break; } if (x >= scratch_ptr[0]) { /* pick the first language as the default */ DPRINTFN(1, "Using first language\n"); langid = UGETW(scratch_ptr + 2); } DPRINTFN(1, "Language selected: 0x%04x\n", langid); udev->langid = langid; } if (do_unlock) usbd_ctrl_unlock(udev); } /*------------------------------------------------------------------------* * usb_alloc_device * * This function allocates a new USB device. This function is called * when a new device has been put in the powered state, but not yet in * the addressed state. Get initial descriptor, set the address, get * full descriptor and get strings. * * Return values: * 0: Failure * Else: Success *------------------------------------------------------------------------*/ struct usb_device * usb_alloc_device(device_t parent_dev, struct usb_bus *bus, struct usb_device *parent_hub, uint8_t depth, uint8_t port_index, uint8_t port_no, enum usb_dev_speed speed, enum usb_hc_mode mode) { struct usb_attach_arg uaa; struct usb_device *udev; struct usb_device *adev; struct usb_device *hub; usb_error_t err; uint8_t device_index; uint8_t config_index; uint8_t config_quirk; uint8_t set_config_failed; DPRINTF("parent_dev=%p, bus=%p, parent_hub=%p, depth=%u, " "port_index=%u, port_no=%u, speed=%u, usb_mode=%u\n", parent_dev, bus, parent_hub, depth, port_index, port_no, speed, mode); /* * Find an unused device index. In USB Host mode this is the * same as the device address. * * Device index zero is not used and device index 1 should * always be the root hub. */ for (device_index = USB_ROOT_HUB_ADDR; (device_index != bus->devices_max) && (bus->devices[device_index] != NULL); device_index++) /* nop */; if (device_index == bus->devices_max) { device_printf(bus->bdev, "No free USB device index for new device\n"); return (NULL); } if (depth > 0x10) { device_printf(bus->bdev, "Invalid device depth\n"); return (NULL); } udev = malloc(sizeof(*udev), M_USB, M_WAITOK | M_ZERO); #if (USB_HAVE_MALLOC_WAITOK == 0) if (udev == NULL) { return (NULL); } #endif /* initialise our SX-lock */ sx_init_flags(&udev->enum_sx, "USB config SX lock", SX_DUPOK); sx_init_flags(&udev->sr_sx, "USB suspend and resume SX lock", SX_NOWITNESS); sx_init_flags(&udev->ctrl_sx, "USB control transfer SX lock", SX_DUPOK); cv_init(&udev->ctrlreq_cv, "WCTRL"); cv_init(&udev->ref_cv, "UGONE"); /* initialise our mutex */ mtx_init(&udev->device_mtx, "USB device mutex", NULL, MTX_DEF); /* initialise generic clear stall */ udev->cs_msg[0].hdr.pm_callback = &usbd_clear_stall_proc; udev->cs_msg[0].udev = udev; udev->cs_msg[1].hdr.pm_callback = &usbd_clear_stall_proc; udev->cs_msg[1].udev = udev; /* initialise some USB device fields */ udev->parent_hub = parent_hub; udev->parent_dev = parent_dev; udev->port_index = port_index; udev->port_no = port_no; udev->depth = depth; udev->bus = bus; udev->address = USB_START_ADDR; /* default value */ udev->plugtime = (usb_ticks_t)ticks; /* * We need to force the power mode to "on" because there are plenty * of USB devices out there that do not work very well with * automatic suspend and resume! */ udev->power_mode = usbd_filter_power_mode(udev, USB_POWER_MODE_ON); udev->pwr_save.last_xfer_time = ticks; /* we are not ready yet */ udev->refcount = 1; /* set up default endpoint descriptor */ udev->ctrl_ep_desc.bLength = sizeof(udev->ctrl_ep_desc); udev->ctrl_ep_desc.bDescriptorType = UDESC_ENDPOINT; udev->ctrl_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; udev->ctrl_ep_desc.bmAttributes = UE_CONTROL; udev->ctrl_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET; udev->ctrl_ep_desc.wMaxPacketSize[1] = 0; udev->ctrl_ep_desc.bInterval = 0; /* set up default endpoint companion descriptor */ udev->ctrl_ep_comp_desc.bLength = sizeof(udev->ctrl_ep_comp_desc); udev->ctrl_ep_comp_desc.bDescriptorType = UDESC_ENDPOINT_SS_COMP; udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET; udev->speed = speed; udev->flags.usb_mode = mode; /* search for our High Speed USB HUB, if any */ adev = udev; hub = udev->parent_hub; while (hub) { if (hub->speed == USB_SPEED_HIGH) { udev->hs_hub_addr = hub->address; udev->parent_hs_hub = hub; udev->hs_port_no = adev->port_no; break; } adev = hub; hub = hub->parent_hub; } /* init the default endpoint */ usb_init_endpoint(udev, 0, &udev->ctrl_ep_desc, &udev->ctrl_ep_comp_desc, &udev->ctrl_ep); /* set device index */ udev->device_index = device_index; #if USB_HAVE_UGEN /* Create ugen name */ snprintf(udev->ugen_name, sizeof(udev->ugen_name), USB_GENERIC_NAME "%u.%u", device_get_unit(bus->bdev), device_index); LIST_INIT(&udev->pd_list); /* Create the control endpoint device */ udev->ctrl_dev = usb_make_dev(udev, NULL, 0, 0, FREAD|FWRITE, UID_ROOT, GID_OPERATOR, 0600); /* Create a link from /dev/ugenX.X to the default endpoint */ if (udev->ctrl_dev != NULL) make_dev_alias(udev->ctrl_dev->cdev, "%s", udev->ugen_name); #endif /* Initialise device */ if (bus->methods->device_init != NULL) { err = (bus->methods->device_init) (udev); if (err != 0) { DPRINTFN(0, "device init %d failed " "(%s, ignored)\n", device_index, usbd_errstr(err)); goto done; } } /* set powered device state after device init is complete */ usb_set_device_state(udev, USB_STATE_POWERED); if (udev->flags.usb_mode == USB_MODE_HOST) { err = usbd_req_set_address(udev, NULL, device_index); /* * This is the new USB device address from now on, if * the set address request didn't set it already. */ if (udev->address == USB_START_ADDR) udev->address = device_index; /* * We ignore any set-address errors, hence there are * buggy USB devices out there that actually receive * the SETUP PID, but manage to set the address before * the STATUS stage is ACK'ed. If the device responds * to the subsequent get-descriptor at the new * address, then we know that the set-address command * was successful. */ if (err) { DPRINTFN(0, "set address %d failed " "(%s, ignored)\n", udev->address, usbd_errstr(err)); } } else { /* We are not self powered */ udev->flags.self_powered = 0; /* Set unconfigured state */ udev->curr_config_no = USB_UNCONFIG_NO; udev->curr_config_index = USB_UNCONFIG_INDEX; /* Setup USB descriptors */ err = (usb_temp_setup_by_index_p) (udev, usb_template); if (err) { DPRINTFN(0, "setting up USB template failed - " "usb_template(4) not loaded?\n"); goto done; } } usb_set_device_state(udev, USB_STATE_ADDRESSED); /* setup the device descriptor and the initial "wMaxPacketSize" */ err = usbd_setup_device_desc(udev, NULL); if (err != 0) { /* try to enumerate two more times */ err = usbd_req_re_enumerate(udev, NULL); if (err != 0) { err = usbd_req_re_enumerate(udev, NULL); if (err != 0) { goto done; } } } /* * Setup temporary USB attach args so that we can figure out some * basic quirks for this device. */ usb_init_attach_arg(udev, &uaa); if (usb_test_quirk(&uaa, UQ_BUS_POWERED)) { udev->flags.uq_bus_powered = 1; } if (usb_test_quirk(&uaa, UQ_NO_STRINGS)) { udev->flags.no_strings = 1; } usb_get_langid(udev); /* assume 100mA bus powered for now. Changed when configured. */ udev->power = USB_MIN_POWER; /* fetch the vendor and product strings from the device */ usb_set_device_strings(udev); if (udev->flags.usb_mode == USB_MODE_DEVICE) { /* USB device mode setup is complete */ err = 0; goto config_done; } /* * Most USB devices should attach to config index 0 by * default */ if (usb_test_quirk(&uaa, UQ_CFG_INDEX_0)) { config_index = 0; config_quirk = 1; } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_1)) { config_index = 1; config_quirk = 1; } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_2)) { config_index = 2; config_quirk = 1; } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_3)) { config_index = 3; config_quirk = 1; } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_4)) { config_index = 4; config_quirk = 1; } else { config_index = 0; config_quirk = 0; } set_config_failed = 0; repeat_set_config: DPRINTF("setting config %u\n", config_index); /* get the USB device configured */ err = usbd_set_config_index(udev, config_index); if (err) { if (udev->ddesc.bNumConfigurations != 0) { if (!set_config_failed) { set_config_failed = 1; /* XXX try to re-enumerate the device */ err = usbd_req_re_enumerate(udev, NULL); if (err == 0) goto repeat_set_config; } DPRINTFN(0, "Failure selecting configuration index %u:" "%s, port %u, addr %u (ignored)\n", config_index, usbd_errstr(err), udev->port_no, udev->address); } /* * Some USB devices do not have any configurations. Ignore any * set config failures! */ err = 0; goto config_done; } if (!config_quirk && config_index + 1 < udev->ddesc.bNumConfigurations) { if ((udev->cdesc->bNumInterface < 2) && usbd_get_no_descriptors(udev->cdesc, UDESC_ENDPOINT) == 0) { DPRINTFN(0, "Found no endpoints, trying next config\n"); config_index++; goto repeat_set_config; } #if USB_HAVE_MSCTEST if (config_index == 0) { /* * Try to figure out if we have an * auto-install disk there: */ if (usb_iface_is_cdrom(udev, 0)) { DPRINTFN(0, "Found possible auto-install " "disk (trying next config)\n"); config_index++; goto repeat_set_config; } } #endif } #if USB_HAVE_MSCTEST if (set_config_failed == 0 && config_index == 0 && + usb_test_quirk(&uaa, UQ_MSC_NO_START_STOP) == 0 && + usb_test_quirk(&uaa, UQ_MSC_NO_PREVENT_ALLOW) == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_SYNC_CACHE) == 0 && + usb_test_quirk(&uaa, UQ_MSC_NO_TEST_UNIT_READY) == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_GETMAXLUN) == 0) { /* * Try to figure out if there are any MSC quirks we * should apply automatically: */ - err = usb_msc_auto_quirk(udev, 0); + err = usb_msc_auto_quirk(udev, 0, &uaa); if (err != 0) { set_config_failed = 1; goto repeat_set_config; } } #endif config_done: DPRINTF("new dev (addr %d), udev=%p, parent_hub=%p\n", udev->address, udev, udev->parent_hub); /* register our device - we are ready */ usb_bus_port_set_device(bus, parent_hub ? parent_hub->hub->ports + port_index : NULL, udev, device_index); #if USB_HAVE_UGEN /* Symlink the ugen device name */ udev->ugen_symlink = usb_alloc_symlink(udev->ugen_name); /* Announce device */ printf("%s: <%s %s> at %s\n", udev->ugen_name, usb_get_manufacturer(udev), usb_get_product(udev), device_get_nameunit(udev->bus->bdev)); #endif #if USB_HAVE_DEVCTL usb_notify_addq("ATTACH", udev); #endif done: if (err) { /* * Free USB device and all subdevices, if any. */ usb_free_device(udev, 0); udev = NULL; } return (udev); } #if USB_HAVE_UGEN struct usb_fs_privdata * usb_make_dev(struct usb_device *udev, const char *devname, int ep, int fi, int rwmode, uid_t uid, gid_t gid, int mode) { struct usb_fs_privdata* pd; struct make_dev_args args; char buffer[32]; /* Store information to locate ourselves again later */ pd = malloc(sizeof(struct usb_fs_privdata), M_USBDEV, M_WAITOK | M_ZERO); pd->bus_index = device_get_unit(udev->bus->bdev); pd->dev_index = udev->device_index; pd->ep_addr = ep; pd->fifo_index = fi; pd->mode = rwmode; /* Now, create the device itself */ if (devname == NULL) { devname = buffer; snprintf(buffer, sizeof(buffer), USB_DEVICE_DIR "/%u.%u.%u", pd->bus_index, pd->dev_index, pd->ep_addr); } /* Setup arguments for make_dev_s() */ make_dev_args_init(&args); args.mda_devsw = &usb_devsw; args.mda_uid = uid; args.mda_gid = gid; args.mda_mode = mode; args.mda_si_drv1 = pd; if (make_dev_s(&args, &pd->cdev, "%s", devname) != 0) { DPRINTFN(0, "Failed to create device %s\n", devname); free(pd, M_USBDEV); return (NULL); } return (pd); } void usb_destroy_dev_sync(struct usb_fs_privdata *pd) { DPRINTFN(1, "Destroying device at ugen%d.%d\n", pd->bus_index, pd->dev_index); /* * Destroy character device synchronously. After this * all system calls are returned. Can block. */ destroy_dev(pd->cdev); free(pd, M_USBDEV); } void usb_destroy_dev(struct usb_fs_privdata *pd) { struct usb_bus *bus; if (pd == NULL) return; mtx_lock(&usb_ref_lock); bus = devclass_get_softc(usb_devclass_ptr, pd->bus_index); mtx_unlock(&usb_ref_lock); if (bus == NULL) { usb_destroy_dev_sync(pd); return; } /* make sure we can re-use the device name */ delist_dev(pd->cdev); USB_BUS_LOCK(bus); LIST_INSERT_HEAD(&bus->pd_cleanup_list, pd, pd_next); /* get cleanup going */ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->cleanup_msg[0], &bus->cleanup_msg[1]); USB_BUS_UNLOCK(bus); } static void usb_cdev_create(struct usb_device *udev) { struct usb_config_descriptor *cd; struct usb_endpoint_descriptor *ed; struct usb_descriptor *desc; struct usb_fs_privdata* pd; int inmode, outmode, inmask, outmask, mode; uint8_t ep; KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("stale cdev entries")); DPRINTFN(2, "Creating device nodes\n"); if (usbd_get_mode(udev) == USB_MODE_DEVICE) { inmode = FWRITE; outmode = FREAD; } else { /* USB_MODE_HOST */ inmode = FREAD; outmode = FWRITE; } inmask = 0; outmask = 0; desc = NULL; /* * Collect all used endpoint numbers instead of just * generating 16 static endpoints. */ cd = usbd_get_config_descriptor(udev); while ((desc = usb_desc_foreach(cd, desc))) { /* filter out all endpoint descriptors */ if ((desc->bDescriptorType == UDESC_ENDPOINT) && (desc->bLength >= sizeof(*ed))) { ed = (struct usb_endpoint_descriptor *)desc; /* update masks */ ep = ed->bEndpointAddress; if (UE_GET_DIR(ep) == UE_DIR_OUT) outmask |= 1 << UE_GET_ADDR(ep); else inmask |= 1 << UE_GET_ADDR(ep); } } /* Create all available endpoints except EP0 */ for (ep = 1; ep < 16; ep++) { mode = (inmask & (1 << ep)) ? inmode : 0; mode |= (outmask & (1 << ep)) ? outmode : 0; if (mode == 0) continue; /* no IN or OUT endpoint */ pd = usb_make_dev(udev, NULL, ep, 0, mode, UID_ROOT, GID_OPERATOR, 0600); if (pd != NULL) LIST_INSERT_HEAD(&udev->pd_list, pd, pd_next); } } static void usb_cdev_free(struct usb_device *udev) { struct usb_fs_privdata* pd; DPRINTFN(2, "Freeing device nodes\n"); while ((pd = LIST_FIRST(&udev->pd_list)) != NULL) { KASSERT(pd->cdev->si_drv1 == pd, ("privdata corrupt")); LIST_REMOVE(pd, pd_next); usb_destroy_dev(pd); } } #endif /*------------------------------------------------------------------------* * usb_free_device * * This function is NULL safe and will free an USB device and its * children devices, if any. * * Flag values: Reserved, set to zero. *------------------------------------------------------------------------*/ void usb_free_device(struct usb_device *udev, uint8_t flag) { struct usb_bus *bus; if (udev == NULL) return; /* already freed */ DPRINTFN(4, "udev=%p port=%d\n", udev, udev->port_no); bus = udev->bus; /* set DETACHED state to prevent any further references */ usb_set_device_state(udev, USB_STATE_DETACHED); #if USB_HAVE_DEVCTL usb_notify_addq("DETACH", udev); #endif #if USB_HAVE_UGEN if (!rebooting) { printf("%s: <%s %s> at %s (disconnected)\n", udev->ugen_name, usb_get_manufacturer(udev), usb_get_product(udev), device_get_nameunit(bus->bdev)); } /* Destroy UGEN symlink, if any */ if (udev->ugen_symlink) { usb_free_symlink(udev->ugen_symlink); udev->ugen_symlink = NULL; } usb_destroy_dev(udev->ctrl_dev); #endif if (udev->flags.usb_mode == USB_MODE_DEVICE) { /* stop receiving any control transfers (Device Side Mode) */ usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); } /* the following will get the device unconfigured in software */ usb_unconfigure(udev, USB_UNCFG_FLAG_FREE_EP0); /* final device unregister after all character devices are closed */ usb_bus_port_set_device(bus, udev->parent_hub ? udev->parent_hub->hub->ports + udev->port_index : NULL, NULL, USB_ROOT_HUB_ADDR); /* unsetup any leftover default USB transfers */ usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); /* template unsetup, if any */ (usb_temp_unsetup_p) (udev); /* * Make sure that our clear-stall messages are not queued * anywhere: */ USB_BUS_LOCK(udev->bus); usb_proc_mwait(USB_BUS_CS_PROC(udev->bus), &udev->cs_msg[0], &udev->cs_msg[1]); USB_BUS_UNLOCK(udev->bus); /* wait for all references to go away */ usb_wait_pending_refs(udev); sx_destroy(&udev->enum_sx); sx_destroy(&udev->sr_sx); sx_destroy(&udev->ctrl_sx); cv_destroy(&udev->ctrlreq_cv); cv_destroy(&udev->ref_cv); mtx_destroy(&udev->device_mtx); #if USB_HAVE_UGEN KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("leaked cdev entries")); #endif /* Uninitialise device */ if (bus->methods->device_uninit != NULL) (bus->methods->device_uninit) (udev); /* free device */ free(udev->serial, M_USB); free(udev->manufacturer, M_USB); free(udev->product, M_USB); free(udev, M_USB); } /*------------------------------------------------------------------------* * usbd_get_iface * * This function is the safe way to get the USB interface structure * pointer by interface index. * * Return values: * NULL: Interface not present. * Else: Pointer to USB interface structure. *------------------------------------------------------------------------*/ struct usb_interface * usbd_get_iface(struct usb_device *udev, uint8_t iface_index) { struct usb_interface *iface = udev->ifaces + iface_index; if (iface_index >= udev->ifaces_max) return (NULL); return (iface); } /*------------------------------------------------------------------------* * usbd_find_descriptor * * This function will lookup the first descriptor that matches the * criteria given by the arguments "type" and "subtype". Descriptors * will only be searched within the interface having the index * "iface_index". If the "id" argument points to an USB descriptor, * it will be skipped before the search is started. This allows * searching for multiple descriptors using the same criteria. Else * the search is started after the interface descriptor. * * Return values: * NULL: End of descriptors * Else: A descriptor matching the criteria *------------------------------------------------------------------------*/ void * usbd_find_descriptor(struct usb_device *udev, void *id, uint8_t iface_index, uint8_t type, uint8_t type_mask, uint8_t subtype, uint8_t subtype_mask) { struct usb_descriptor *desc; struct usb_config_descriptor *cd; struct usb_interface *iface; cd = usbd_get_config_descriptor(udev); if (cd == NULL) { return (NULL); } if (id == NULL) { iface = usbd_get_iface(udev, iface_index); if (iface == NULL) { return (NULL); } id = usbd_get_interface_descriptor(iface); if (id == NULL) { return (NULL); } } desc = (void *)id; while ((desc = usb_desc_foreach(cd, desc))) { if (desc->bDescriptorType == UDESC_INTERFACE) { break; } if (((desc->bDescriptorType & type_mask) == type) && ((desc->bDescriptorSubtype & subtype_mask) == subtype)) { return (desc); } } return (NULL); } /*------------------------------------------------------------------------* * usb_devinfo * * This function will dump information from the device descriptor * belonging to the USB device pointed to by "udev", to the string * pointed to by "dst_ptr" having a maximum length of "dst_len" bytes * including the terminating zero. *------------------------------------------------------------------------*/ void usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len) { struct usb_device_descriptor *udd = &udev->ddesc; uint16_t bcdDevice; uint16_t bcdUSB; bcdUSB = UGETW(udd->bcdUSB); bcdDevice = UGETW(udd->bcdDevice); if (udd->bDeviceClass != 0xFF) { snprintf(dst_ptr, dst_len, "%s %s, class %d/%d, rev %x.%02x/" "%x.%02x, addr %d", usb_get_manufacturer(udev), usb_get_product(udev), udd->bDeviceClass, udd->bDeviceSubClass, (bcdUSB >> 8), bcdUSB & 0xFF, (bcdDevice >> 8), bcdDevice & 0xFF, udev->address); } else { snprintf(dst_ptr, dst_len, "%s %s, rev %x.%02x/" "%x.%02x, addr %d", usb_get_manufacturer(udev), usb_get_product(udev), (bcdUSB >> 8), bcdUSB & 0xFF, (bcdDevice >> 8), bcdDevice & 0xFF, udev->address); } } #ifdef USB_VERBOSE /* * Descriptions of of known vendors and devices ("products"). */ struct usb_knowndev { uint16_t vendor; uint16_t product; uint32_t flags; const char *vendorname; const char *productname; }; #define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */ #include "usbdevs.h" #include "usbdevs_data.h" #endif /* USB_VERBOSE */ void usb_set_device_strings(struct usb_device *udev) { struct usb_device_descriptor *udd = &udev->ddesc; #ifdef USB_VERBOSE const struct usb_knowndev *kdp; #endif char *temp_ptr; size_t temp_size; uint16_t vendor_id; uint16_t product_id; uint8_t do_unlock; /* Protect scratch area */ do_unlock = usbd_ctrl_lock(udev); temp_ptr = (char *)udev->scratch.data; temp_size = sizeof(udev->scratch.data); vendor_id = UGETW(udd->idVendor); product_id = UGETW(udd->idProduct); /* cleanup old strings, if any */ free(udev->serial, M_USB); free(udev->manufacturer, M_USB); free(udev->product, M_USB); /* zero the string pointers */ udev->serial = NULL; udev->manufacturer = NULL; udev->product = NULL; /* get serial number string */ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, udev->ddesc.iSerialNumber); udev->serial = strdup(temp_ptr, M_USB); /* get manufacturer string */ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, udev->ddesc.iManufacturer); usb_trim_spaces(temp_ptr); if (temp_ptr[0] != '\0') udev->manufacturer = strdup(temp_ptr, M_USB); /* get product string */ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, udev->ddesc.iProduct); usb_trim_spaces(temp_ptr); if (temp_ptr[0] != '\0') udev->product = strdup(temp_ptr, M_USB); #ifdef USB_VERBOSE if (udev->manufacturer == NULL || udev->product == NULL) { for (kdp = usb_knowndevs; kdp->vendorname != NULL; kdp++) { if (kdp->vendor == vendor_id && (kdp->product == product_id || (kdp->flags & USB_KNOWNDEV_NOPROD) != 0)) break; } if (kdp->vendorname != NULL) { /* XXX should use pointer to knowndevs string */ if (udev->manufacturer == NULL) { udev->manufacturer = strdup(kdp->vendorname, M_USB); } if (udev->product == NULL && (kdp->flags & USB_KNOWNDEV_NOPROD) == 0) { udev->product = strdup(kdp->productname, M_USB); } } } #endif /* Provide default strings if none were found */ if (udev->manufacturer == NULL) { snprintf(temp_ptr, temp_size, "vendor 0x%04x", vendor_id); udev->manufacturer = strdup(temp_ptr, M_USB); } if (udev->product == NULL) { snprintf(temp_ptr, temp_size, "product 0x%04x", product_id); udev->product = strdup(temp_ptr, M_USB); } if (do_unlock) usbd_ctrl_unlock(udev); } /* * Returns: * See: USB_MODE_XXX */ enum usb_hc_mode usbd_get_mode(struct usb_device *udev) { return (udev->flags.usb_mode); } /* * Returns: * See: USB_SPEED_XXX */ enum usb_dev_speed usbd_get_speed(struct usb_device *udev) { return (udev->speed); } uint32_t usbd_get_isoc_fps(struct usb_device *udev) { ; /* indent fix */ switch (udev->speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: return (1000); default: return (8000); } } struct usb_device_descriptor * usbd_get_device_descriptor(struct usb_device *udev) { if (udev == NULL) return (NULL); /* be NULL safe */ return (&udev->ddesc); } struct usb_config_descriptor * usbd_get_config_descriptor(struct usb_device *udev) { if (udev == NULL) return (NULL); /* be NULL safe */ return (udev->cdesc); } /*------------------------------------------------------------------------* * usb_test_quirk - test a device for a given quirk * * Return values: * 0: The USB device does not have the given quirk. * Else: The USB device has the given quirk. *------------------------------------------------------------------------*/ uint8_t usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk) { uint8_t found; uint8_t x; if (quirk == UQ_NONE) return (0); /* search the automatic per device quirks first */ for (x = 0; x != USB_MAX_AUTO_QUIRK; x++) { if (uaa->device->autoQuirk[x] == quirk) return (1); } /* search global quirk table, if any */ found = (usb_test_quirk_p) (&uaa->info, quirk); return (found); } struct usb_interface_descriptor * usbd_get_interface_descriptor(struct usb_interface *iface) { if (iface == NULL) return (NULL); /* be NULL safe */ return (iface->idesc); } uint8_t usbd_get_interface_altindex(struct usb_interface *iface) { return (iface->alt_index); } uint8_t usbd_get_bus_index(struct usb_device *udev) { return ((uint8_t)device_get_unit(udev->bus->bdev)); } uint8_t usbd_get_device_index(struct usb_device *udev) { return (udev->device_index); } #if USB_HAVE_DEVCTL static void usb_notify_addq(const char *type, struct usb_device *udev) { struct usb_interface *iface; struct sbuf *sb; int i; /* announce the device */ sb = sbuf_new_auto(); sbuf_printf(sb, #if USB_HAVE_UGEN "ugen=%s " "cdev=%s " #endif "vendor=0x%04x " "product=0x%04x " "devclass=0x%02x " "devsubclass=0x%02x " "sernum=\"%s\" " "release=0x%04x " "mode=%s " "port=%u " #if USB_HAVE_UGEN "parent=%s" #endif "", #if USB_HAVE_UGEN udev->ugen_name, udev->ugen_name, #endif UGETW(udev->ddesc.idVendor), UGETW(udev->ddesc.idProduct), udev->ddesc.bDeviceClass, udev->ddesc.bDeviceSubClass, usb_get_serial(udev), UGETW(udev->ddesc.bcdDevice), (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", udev->port_no #if USB_HAVE_UGEN , udev->parent_hub != NULL ? udev->parent_hub->ugen_name : device_get_nameunit(device_get_parent(udev->bus->bdev)) #endif ); sbuf_finish(sb); devctl_notify("USB", "DEVICE", type, sbuf_data(sb)); sbuf_delete(sb); /* announce each interface */ for (i = 0; i < USB_IFACE_MAX; i++) { iface = usbd_get_iface(udev, i); if (iface == NULL) break; /* end of interfaces */ if (iface->idesc == NULL) continue; /* no interface descriptor */ sb = sbuf_new_auto(); sbuf_printf(sb, #if USB_HAVE_UGEN "ugen=%s " "cdev=%s " #endif "vendor=0x%04x " "product=0x%04x " "devclass=0x%02x " "devsubclass=0x%02x " "sernum=\"%s\" " "release=0x%04x " "mode=%s " "interface=%d " "endpoints=%d " "intclass=0x%02x " "intsubclass=0x%02x " "intprotocol=0x%02x", #if USB_HAVE_UGEN udev->ugen_name, udev->ugen_name, #endif UGETW(udev->ddesc.idVendor), UGETW(udev->ddesc.idProduct), udev->ddesc.bDeviceClass, udev->ddesc.bDeviceSubClass, usb_get_serial(udev), UGETW(udev->ddesc.bcdDevice), (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", iface->idesc->bInterfaceNumber, iface->idesc->bNumEndpoints, iface->idesc->bInterfaceClass, iface->idesc->bInterfaceSubClass, iface->idesc->bInterfaceProtocol); sbuf_finish(sb); devctl_notify("USB", "INTERFACE", type, sbuf_data(sb)); sbuf_delete(sb); } } #endif #if USB_HAVE_UGEN /*------------------------------------------------------------------------* * usb_fifo_free_wrap * * This function will free the FIFOs. * * Description of "flag" argument: If the USB_UNCFG_FLAG_FREE_EP0 flag * is set and "iface_index" is set to "USB_IFACE_INDEX_ANY", we free * all FIFOs. If the USB_UNCFG_FLAG_FREE_EP0 flag is not set and * "iface_index" is set to "USB_IFACE_INDEX_ANY", we free all non * control endpoint FIFOs. If "iface_index" is not set to * "USB_IFACE_INDEX_ANY" the flag has no effect. *------------------------------------------------------------------------*/ static void usb_fifo_free_wrap(struct usb_device *udev, uint8_t iface_index, uint8_t flag) { struct usb_fifo *f; uint16_t i; /* * Free any USB FIFOs on the given interface: */ for (i = 0; i != USB_FIFO_MAX; i++) { f = udev->fifo[i]; if (f == NULL) { continue; } /* Check if the interface index matches */ if (iface_index == f->iface_index) { if (f->methods != &usb_ugen_methods) { /* * Don't free any non-generic FIFOs in * this case. */ continue; } if ((f->dev_ep_index == 0) && (f->fs_xfer == NULL)) { /* no need to free this FIFO */ continue; } } else if (iface_index == USB_IFACE_INDEX_ANY) { if ((f->methods == &usb_ugen_methods) && (f->dev_ep_index == 0) && (!(flag & USB_UNCFG_FLAG_FREE_EP0)) && (f->fs_xfer == NULL)) { /* no need to free this FIFO */ continue; } } else { /* no need to free this FIFO */ continue; } /* free this FIFO */ usb_fifo_free(f); } } #endif /*------------------------------------------------------------------------* * usb_peer_can_wakeup * * Return values: * 0: Peer cannot do resume signalling. * Else: Peer can do resume signalling. *------------------------------------------------------------------------*/ uint8_t usb_peer_can_wakeup(struct usb_device *udev) { const struct usb_config_descriptor *cdp; cdp = udev->cdesc; if ((cdp != NULL) && (udev->flags.usb_mode == USB_MODE_HOST)) { return (cdp->bmAttributes & UC_REMOTE_WAKEUP); } return (0); /* not supported */ } void usb_set_device_state(struct usb_device *udev, enum usb_dev_state state) { KASSERT(state < USB_STATE_MAX, ("invalid udev state")); DPRINTF("udev %p state %s -> %s\n", udev, usb_statestr(udev->state), usb_statestr(state)); #if USB_HAVE_UGEN mtx_lock(&usb_ref_lock); #endif udev->state = state; #if USB_HAVE_UGEN mtx_unlock(&usb_ref_lock); #endif if (udev->bus->methods->device_state_change != NULL) (udev->bus->methods->device_state_change) (udev); } enum usb_dev_state usb_get_device_state(struct usb_device *udev) { if (udev == NULL) return (USB_STATE_DETACHED); return (udev->state); } uint8_t usbd_device_attached(struct usb_device *udev) { return (udev->state > USB_STATE_DETACHED); } /* * The following function locks enumerating the given USB device. If * the lock is already grabbed this function returns zero. Else a * a value of one is returned. */ uint8_t usbd_enum_lock(struct usb_device *udev) { if (sx_xlocked(&udev->enum_sx)) return (0); sx_xlock(&udev->enum_sx); sx_xlock(&udev->sr_sx); /* * NEWBUS LOCK NOTE: We should check if any parent SX locks * are locked before locking Giant. Else the lock can be * locked multiple times. */ mtx_lock(&Giant); return (1); } #if USB_HAVE_UGEN /* * This function is the same like usbd_enum_lock() except a value of * 255 is returned when a signal is pending: */ uint8_t usbd_enum_lock_sig(struct usb_device *udev) { if (sx_xlocked(&udev->enum_sx)) return (0); if (sx_xlock_sig(&udev->enum_sx)) return (255); if (sx_xlock_sig(&udev->sr_sx)) { sx_xunlock(&udev->enum_sx); return (255); } mtx_lock(&Giant); return (1); } #endif /* The following function unlocks enumerating the given USB device. */ void usbd_enum_unlock(struct usb_device *udev) { mtx_unlock(&Giant); sx_xunlock(&udev->enum_sx); sx_xunlock(&udev->sr_sx); } /* The following function locks suspend and resume. */ void usbd_sr_lock(struct usb_device *udev) { sx_xlock(&udev->sr_sx); /* * NEWBUS LOCK NOTE: We should check if any parent SX locks * are locked before locking Giant. Else the lock can be * locked multiple times. */ mtx_lock(&Giant); } /* The following function unlocks suspend and resume. */ void usbd_sr_unlock(struct usb_device *udev) { mtx_unlock(&Giant); sx_xunlock(&udev->sr_sx); } /* * The following function checks the enumerating lock for the given * USB device. */ uint8_t usbd_enum_is_locked(struct usb_device *udev) { return (sx_xlocked(&udev->enum_sx)); } /* * The following function is used to serialize access to USB control * transfers and the USB scratch area. If the lock is already grabbed * this function returns zero. Else a value of one is returned. */ uint8_t usbd_ctrl_lock(struct usb_device *udev) { if (sx_xlocked(&udev->ctrl_sx)) return (0); sx_xlock(&udev->ctrl_sx); /* * We need to allow suspend and resume at this point, else the * control transfer will timeout if the device is suspended! */ if (usbd_enum_is_locked(udev)) usbd_sr_unlock(udev); return (1); } void usbd_ctrl_unlock(struct usb_device *udev) { sx_xunlock(&udev->ctrl_sx); /* * Restore the suspend and resume lock after we have unlocked * the USB control transfer lock to avoid LOR: */ if (usbd_enum_is_locked(udev)) usbd_sr_lock(udev); } /* * The following function is used to set the per-interface specific * plug and play information. The string referred to by the pnpinfo * argument can safely be freed after calling this function. The * pnpinfo of an interface will be reset at device detach or when * passing a NULL argument to this function. This function * returns zero on success, else a USB_ERR_XXX failure code. */ usb_error_t usbd_set_pnpinfo(struct usb_device *udev, uint8_t iface_index, const char *pnpinfo) { struct usb_interface *iface; iface = usbd_get_iface(udev, iface_index); if (iface == NULL) return (USB_ERR_INVAL); if (iface->pnpinfo != NULL) { free(iface->pnpinfo, M_USBDEV); iface->pnpinfo = NULL; } if (pnpinfo == NULL || pnpinfo[0] == 0) return (0); /* success */ iface->pnpinfo = strdup(pnpinfo, M_USBDEV); if (iface->pnpinfo == NULL) return (USB_ERR_NOMEM); return (0); /* success */ } usb_error_t usbd_add_dynamic_quirk(struct usb_device *udev, uint16_t quirk) { uint8_t x; for (x = 0; x != USB_MAX_AUTO_QUIRK; x++) { if (udev->autoQuirk[x] == 0 || udev->autoQuirk[x] == quirk) { udev->autoQuirk[x] = quirk; return (0); /* success */ } } return (USB_ERR_NOMEM); } /* * The following function is used to select the endpoint mode. It * should not be called outside enumeration context. */ usb_error_t usbd_set_endpoint_mode(struct usb_device *udev, struct usb_endpoint *ep, uint8_t ep_mode) { usb_error_t error; uint8_t do_unlock; /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); if (udev->bus->methods->set_endpoint_mode != NULL) { error = (udev->bus->methods->set_endpoint_mode) ( udev, ep, ep_mode); } else if (ep_mode != USB_EP_MODE_DEFAULT) { error = USB_ERR_INVAL; } else { error = 0; } /* only set new mode regardless of error */ ep->ep_mode = ep_mode; if (do_unlock) usbd_enum_unlock(udev); return (error); } uint8_t usbd_get_endpoint_mode(struct usb_device *udev, struct usb_endpoint *ep) { return (ep->ep_mode); } diff --git a/sys/dev/usb/usb_msctest.c b/sys/dev/usb/usb_msctest.c index 0fffd99a73c4..5dcf8d151119 100644 --- a/sys/dev/usb/usb_msctest.c +++ b/sys/dev/usb/usb_msctest.c @@ -1,1107 +1,1140 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2008,2011 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2008-2022 Hans Petter Selasky. + * Copyright (c) 2021-2022 Idwer Vollering. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * The following file contains code that will detect USB autoinstall * disks. - * - * TODO: Potentially we could add code to automatically detect USB - * mass storage quirks for not supported SCSI commands! */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usb_debug #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { ST_COMMAND, ST_DATA_RD, ST_DATA_RD_CS, ST_DATA_WR, ST_DATA_WR_CS, ST_STATUS, ST_MAX, }; enum { DIR_IN, DIR_OUT, DIR_NONE, }; #define SCSI_MAX_LEN MAX(SCSI_FIXED_BLOCK_SIZE, USB_MSCTEST_BULK_SIZE) #define SCSI_INQ_LEN 0x24 #define SCSI_SENSE_LEN 0xFF #define SCSI_FIXED_BLOCK_SIZE 512 /* bytes */ static uint8_t scsi_test_unit_ready[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_inquiry[] = { 0x12, 0x00, 0x00, 0x00, SCSI_INQ_LEN, 0x00 }; static uint8_t scsi_rezero_init[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; -static uint8_t scsi_start_stop_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00 }; +static uint8_t scsi_start_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00 }; +static uint8_t scsi_stop_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00 }; static uint8_t scsi_ztestor_eject[] = { 0x85, 0x01, 0x01, 0x01, 0x18, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00 }; static uint8_t scsi_cmotech_eject[] = { 0xff, 0x52, 0x44, 0x45, 0x56, 0x43, 0x48, 0x47 }; static uint8_t scsi_huawei_eject[] = { 0x11, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_huawei_eject2[] = { 0x11, 0x06, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_tct_eject[] = { 0x06, 0xf5, 0x04, 0x02, 0x52, 0x70 }; static uint8_t scsi_sync_cache[] = { 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_request_sense[] = { 0x03, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_read_capacity[] = { 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_prevent_removal[] = { 0x1e, 0, 0, 0, 1, 0 }; static uint8_t scsi_allow_removal[] = { 0x1e, 0, 0, 0, 0, 0 }; #ifndef USB_MSCTEST_BULK_SIZE #define USB_MSCTEST_BULK_SIZE 64 /* dummy */ #endif #define ERR_CSW_FAILED -1 /* Command Block Wrapper */ struct bbb_cbw { uDWord dCBWSignature; #define CBWSIGNATURE 0x43425355 uDWord dCBWTag; uDWord dCBWDataTransferLength; uByte bCBWFlags; #define CBWFLAGS_OUT 0x00 #define CBWFLAGS_IN 0x80 uByte bCBWLUN; uByte bCDBLength; #define CBWCDBLENGTH 16 uByte CBWCDB[CBWCDBLENGTH]; } __packed; /* Command Status Wrapper */ struct bbb_csw { uDWord dCSWSignature; #define CSWSIGNATURE 0x53425355 uDWord dCSWTag; uDWord dCSWDataResidue; uByte bCSWStatus; #define CSWSTATUS_GOOD 0x0 #define CSWSTATUS_FAILED 0x1 #define CSWSTATUS_PHASE 0x2 } __packed; struct bbb_transfer { struct mtx mtx; struct cv cv; struct bbb_cbw *cbw; struct bbb_csw *csw; struct usb_xfer *xfer[ST_MAX]; uint8_t *data_ptr; usb_size_t data_len; /* bytes */ usb_size_t data_rem; /* bytes */ usb_timeout_t data_timeout; /* ms */ usb_frlength_t actlen; /* bytes */ usb_frlength_t buffer_size; /* bytes */ uint8_t cmd_len; /* bytes */ uint8_t dir; uint8_t lun; uint8_t state; uint8_t status_try; int error; uint8_t *buffer; }; static usb_callback_t bbb_command_callback; static usb_callback_t bbb_data_read_callback; static usb_callback_t bbb_data_rd_cs_callback; static usb_callback_t bbb_data_write_callback; static usb_callback_t bbb_data_wr_cs_callback; static usb_callback_t bbb_status_callback; static usb_callback_t bbb_raw_write_callback; static void bbb_done(struct bbb_transfer *, int); static void bbb_transfer_start(struct bbb_transfer *, uint8_t); static void bbb_data_clear_stall_callback(struct usb_xfer *, uint8_t, uint8_t); static int bbb_command_start(struct bbb_transfer *, uint8_t, uint8_t, void *, size_t, void *, size_t, usb_timeout_t); static struct bbb_transfer *bbb_attach(struct usb_device *, uint8_t, uint8_t); static void bbb_detach(struct bbb_transfer *); static const struct usb_config bbb_config[ST_MAX] = { [ST_COMMAND] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = sizeof(struct bbb_cbw), .callback = &bbb_command_callback, .timeout = 4 * USB_MS_HZ, /* 4 seconds */ }, [ST_DATA_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = SCSI_MAX_LEN, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, .callback = &bbb_data_read_callback, .timeout = 4 * USB_MS_HZ, /* 4 seconds */ }, [ST_DATA_RD_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &bbb_data_rd_cs_callback, .timeout = 1 * USB_MS_HZ, /* 1 second */ }, [ST_DATA_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = SCSI_MAX_LEN, .flags = {.ext_buffer = 1,.proxy_buffer = 1,}, .callback = &bbb_data_write_callback, .timeout = 4 * USB_MS_HZ, /* 4 seconds */ }, [ST_DATA_WR_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &bbb_data_wr_cs_callback, .timeout = 1 * USB_MS_HZ, /* 1 second */ }, [ST_STATUS] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = sizeof(struct bbb_csw), .flags = {.short_xfer_ok = 1,}, .callback = &bbb_status_callback, .timeout = 1 * USB_MS_HZ, /* 1 second */ }, }; static const struct usb_config bbb_raw_config[1] = { [0] = { .type = UE_BULK_INTR, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = SCSI_MAX_LEN, .flags = {.ext_buffer = 1,.proxy_buffer = 1,}, .callback = &bbb_raw_write_callback, .timeout = 1 * USB_MS_HZ, /* 1 second */ }, }; static void bbb_done(struct bbb_transfer *sc, int error) { sc->error = error; sc->state = ST_COMMAND; sc->status_try = 1; cv_signal(&sc->cv); } static void bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index) { sc->state = xfer_index; usbd_transfer_start(sc->xfer[xfer_index]); } static void bbb_data_clear_stall_callback(struct usb_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); if (usbd_clear_stall_callback(xfer, sc->xfer[stall_xfer])) { switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: bbb_transfer_start(sc, next_xfer); break; default: bbb_done(sc, USB_ERR_STALLED); break; } } } static void bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); uint32_t tag; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: bbb_transfer_start (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD : (sc->dir == DIR_OUT) ? ST_DATA_WR : ST_STATUS)); break; case USB_ST_SETUP: sc->status_try = 0; tag = UGETDW(sc->cbw->dCBWTag) + 1; USETDW(sc->cbw->dCBWSignature, CBWSIGNATURE); USETDW(sc->cbw->dCBWTag, tag); USETDW(sc->cbw->dCBWDataTransferLength, (uint32_t)sc->data_len); sc->cbw->bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT); sc->cbw->bCBWLUN = sc->lun; sc->cbw->bCDBLength = sc->cmd_len; if (sc->cbw->bCDBLength > sizeof(sc->cbw->CBWCDB)) { sc->cbw->bCDBLength = sizeof(sc->cbw->CBWCDB); DPRINTFN(0, "Truncating long command\n"); } usbd_xfer_set_frame_len(xfer, 0, sizeof(struct bbb_cbw)); usbd_transfer_submit(xfer); break; default: /* Error */ bbb_done(sc, error); break; } } static void bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->data_rem -= actlen; sc->data_ptr += actlen; sc->actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->data_rem = 0; } case USB_ST_SETUP: DPRINTF("max_bulk=%d, data_rem=%d\n", max_bulk, sc->data_rem); if (sc->data_rem == 0) { bbb_transfer_start(sc, ST_STATUS); break; } if (max_bulk > sc->data_rem) { max_bulk = sc->data_rem; } usbd_xfer_set_timeout(xfer, sc->data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) { bbb_done(sc, error); } else { bbb_transfer_start(sc, ST_DATA_RD_CS); } break; } } static void bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) { bbb_data_clear_stall_callback(xfer, ST_STATUS, ST_DATA_RD); } static void bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->data_rem -= actlen; sc->data_ptr += actlen; sc->actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->data_rem = 0; } case USB_ST_SETUP: DPRINTF("max_bulk=%d, data_rem=%d\n", max_bulk, sc->data_rem); if (sc->data_rem == 0) { bbb_transfer_start(sc, ST_STATUS); break; } if (max_bulk > sc->data_rem) { max_bulk = sc->data_rem; } usbd_xfer_set_timeout(xfer, sc->data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) { bbb_done(sc, error); } else { bbb_transfer_start(sc, ST_DATA_WR_CS); } break; } } static void bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) { bbb_data_clear_stall_callback(xfer, ST_STATUS, ST_DATA_WR); } static void bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); int actlen; int sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* very simple status check */ if (actlen < (int)sizeof(struct bbb_csw)) { bbb_done(sc, USB_ERR_SHORT_XFER); } else if (sc->csw->bCSWStatus == CSWSTATUS_GOOD) { bbb_done(sc, 0); /* success */ } else { bbb_done(sc, ERR_CSW_FAILED); /* error */ } break; case USB_ST_SETUP: usbd_xfer_set_frame_len(xfer, 0, sizeof(struct bbb_csw)); usbd_transfer_submit(xfer); break; default: DPRINTF("Failed to read CSW: %s, try %d\n", usbd_errstr(error), sc->status_try); if (error == USB_ERR_CANCELLED || sc->status_try) { bbb_done(sc, error); } else { sc->status_try = 1; bbb_transfer_start(sc, ST_DATA_RD_CS); } break; } } static void bbb_raw_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->data_rem -= actlen; sc->data_ptr += actlen; sc->actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->data_rem = 0; } case USB_ST_SETUP: DPRINTF("max_bulk=%d, data_rem=%d\n", max_bulk, sc->data_rem); if (sc->data_rem == 0) { bbb_done(sc, 0); break; } if (max_bulk > sc->data_rem) { max_bulk = sc->data_rem; } usbd_xfer_set_timeout(xfer, sc->data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ bbb_done(sc, error); break; } } /*------------------------------------------------------------------------* * bbb_command_start - execute a SCSI command synchronously * * Return values * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun, void *data_ptr, size_t data_len, void *cmd_ptr, size_t cmd_len, usb_timeout_t data_timeout) { sc->lun = lun; sc->dir = data_len ? dir : DIR_NONE; sc->data_ptr = data_ptr; sc->data_len = data_len; sc->data_rem = data_len; sc->data_timeout = (data_timeout + USB_MS_HZ); sc->actlen = 0; sc->error = 0; sc->cmd_len = cmd_len; memset(&sc->cbw->CBWCDB, 0, sizeof(sc->cbw->CBWCDB)); memcpy(&sc->cbw->CBWCDB, cmd_ptr, cmd_len); DPRINTFN(1, "SCSI cmd = %*D\n", (int)cmd_len, (char *)sc->cbw->CBWCDB, ":"); USB_MTX_LOCK(&sc->mtx); usbd_transfer_start(sc->xfer[sc->state]); while (usbd_transfer_pending(sc->xfer[sc->state])) { cv_wait(&sc->cv, &sc->mtx); } USB_MTX_UNLOCK(&sc->mtx); return (sc->error); } /*------------------------------------------------------------------------* * bbb_raw_write - write a raw BULK message synchronously * * Return values * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int bbb_raw_write(struct bbb_transfer *sc, const void *data_ptr, size_t data_len, usb_timeout_t data_timeout) { sc->data_ptr = __DECONST(void *, data_ptr); sc->data_len = data_len; sc->data_rem = data_len; sc->data_timeout = (data_timeout + USB_MS_HZ); sc->actlen = 0; sc->error = 0; DPRINTFN(1, "BULK DATA = %*D\n", (int)data_len, (const char *)data_ptr, ":"); USB_MTX_LOCK(&sc->mtx); usbd_transfer_start(sc->xfer[0]); while (usbd_transfer_pending(sc->xfer[0])) cv_wait(&sc->cv, &sc->mtx); USB_MTX_UNLOCK(&sc->mtx); return (sc->error); } static struct bbb_transfer * bbb_attach(struct usb_device *udev, uint8_t iface_index, uint8_t bInterfaceClass) { struct usb_interface *iface; struct usb_interface_descriptor *id; const struct usb_config *pconfig; struct bbb_transfer *sc; usb_error_t err; int nconfig; #if USB_HAVE_MSCTEST_DETACH uint8_t do_unlock; /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); /* * Make sure any driver which is hooked up to this interface, * like umass is gone: */ usb_detach_device(udev, iface_index, 0); if (do_unlock) usbd_enum_unlock(udev); #endif iface = usbd_get_iface(udev, iface_index); if (iface == NULL) return (NULL); id = iface->idesc; if (id == NULL || id->bInterfaceClass != bInterfaceClass) return (NULL); switch (id->bInterfaceClass) { case UICLASS_MASS: switch (id->bInterfaceSubClass) { case UISUBCLASS_SCSI: case UISUBCLASS_UFI: case UISUBCLASS_SFF8020I: case UISUBCLASS_SFF8070I: break; default: return (NULL); } switch (id->bInterfaceProtocol) { case UIPROTO_MASS_BBB_OLD: case UIPROTO_MASS_BBB: break; default: return (NULL); } pconfig = bbb_config; nconfig = ST_MAX; break; case UICLASS_HID: switch (id->bInterfaceSubClass) { case 0: break; default: return (NULL); } pconfig = bbb_raw_config; nconfig = 1; break; default: return (NULL); } sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO); mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF); cv_init(&sc->cv, "WBBB"); err = usbd_transfer_setup(udev, &iface_index, sc->xfer, pconfig, nconfig, sc, &sc->mtx); if (err) { bbb_detach(sc); return (NULL); } switch (id->bInterfaceClass) { case UICLASS_MASS: /* store pointer to DMA buffers */ sc->buffer = usbd_xfer_get_frame_buffer( sc->xfer[ST_DATA_RD], 0); sc->buffer_size = usbd_xfer_max_len(sc->xfer[ST_DATA_RD]); sc->cbw = usbd_xfer_get_frame_buffer( sc->xfer[ST_COMMAND], 0); sc->csw = usbd_xfer_get_frame_buffer( sc->xfer[ST_STATUS], 0); break; default: break; } return (sc); } static void bbb_detach(struct bbb_transfer *sc) { usbd_transfer_unsetup(sc->xfer, ST_MAX); mtx_destroy(&sc->mtx); cv_destroy(&sc->cv); free(sc, M_USB); } /*------------------------------------------------------------------------* * usb_iface_is_cdrom * * Return values: * 1: This interface is an auto install disk (CD-ROM) * 0: Not an auto install disk. *------------------------------------------------------------------------*/ int usb_iface_is_cdrom(struct usb_device *udev, uint8_t iface_index) { struct bbb_transfer *sc; uint8_t timeout; uint8_t is_cdrom; uint8_t sid_type; int err; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (0); is_cdrom = 0; timeout = 4; /* tries */ while (--timeout) { err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), USB_MS_HZ); if (err == 0 && sc->actlen > 0) { sid_type = sc->buffer[0] & 0x1F; if (sid_type == 0x05) is_cdrom = 1; break; } else if (err != ERR_CSW_FAILED) break; /* non retryable error */ usb_pause_mtx(NULL, hz); } bbb_detach(sc); return (is_cdrom); } static uint8_t usb_msc_get_max_lun(struct usb_device *udev, uint8_t iface_index) { struct usb_device_request req; usb_error_t err; uint8_t buf = 0; /* The Get Max Lun command is a class-specific request. */ req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = 0xFE; /* GET_MAX_LUN */ USETW(req.wValue, 0); req.wIndex[0] = iface_index; req.wIndex[1] = 0; USETW(req.wLength, 1); err = usbd_do_request(udev, NULL, &req, &buf); if (err) buf = 0; return (buf); } +#define USB_ADD_QUIRK(udev, any, which) do { \ + if (usb_get_manufacturer(udev) != NULL && usb_get_product(udev) != NULL) { \ + DPRINTFN(0, #which " set for USB mass storage device %s %s (0x%04x:0x%04x)\n", \ + usb_get_manufacturer(udev), \ + usb_get_product(udev), \ + UGETW(udev->ddesc.idVendor), \ + UGETW(udev->ddesc.idProduct)); \ + } else { \ + DPRINTFN(0, #which " set for USB mass storage device, 0x%04x:0x%04x\n", \ + UGETW(udev->ddesc.idVendor), \ + UGETW(udev->ddesc.idProduct)); \ + } \ + usbd_add_dynamic_quirk(udev, which); \ + any = 1; \ +} while (0) + usb_error_t -usb_msc_auto_quirk(struct usb_device *udev, uint8_t iface_index) +usb_msc_auto_quirk(struct usb_device *udev, uint8_t iface_index, + const struct usb_attach_arg *uaa) { struct bbb_transfer *sc; uint8_t timeout; uint8_t is_no_direct; uint8_t sid_type; + uint8_t any_quirk; int err; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (0); + any_quirk = 0; + /* * Some devices need a delay after that the configuration * value is set to function properly: */ usb_pause_mtx(NULL, hz); - if (usb_msc_get_max_lun(udev, iface_index) == 0) { + if (usb_test_quirk(uaa, UQ_MSC_NO_GETMAXLUN) == 0 && + usb_msc_get_max_lun(udev, iface_index) == 0) { DPRINTF("Device has only got one LUN.\n"); - usbd_add_dynamic_quirk(udev, UQ_MSC_NO_GETMAXLUN); + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_GETMAXLUN); } is_no_direct = 1; for (timeout = 4; timeout != 0; timeout--) { err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), USB_MS_HZ); if (err == 0 && sc->actlen > 0) { sid_type = sc->buffer[0] & 0x1F; if (sid_type == 0x00) is_no_direct = 0; break; } else if (err != ERR_CSW_FAILED) { DPRINTF("Device is not responding " "properly to SCSI INQUIRY command.\n"); goto error; /* non retryable error */ } usb_pause_mtx(NULL, hz); } if (is_no_direct) { DPRINTF("Device is not direct access.\n"); goto done; } - err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, - &scsi_test_unit_ready, sizeof(scsi_test_unit_ready), - USB_MS_HZ); + if (usb_test_quirk(uaa, UQ_MSC_NO_TEST_UNIT_READY) == 0) { + err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, + &scsi_test_unit_ready, sizeof(scsi_test_unit_ready), + USB_MS_HZ); - if (err != 0) { - if (err != ERR_CSW_FAILED) - goto error; - DPRINTF("Test unit ready failed\n"); + if (err != 0) { + if (err != ERR_CSW_FAILED) + goto error; + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_TEST_UNIT_READY); + } } - err = bbb_command_start(sc, DIR_OUT, 0, NULL, 0, - &scsi_prevent_removal, sizeof(scsi_prevent_removal), - USB_MS_HZ); - - if (err == 0) { - err = bbb_command_start(sc, DIR_OUT, 0, NULL, 0, - &scsi_allow_removal, sizeof(scsi_allow_removal), + if (usb_test_quirk(uaa, UQ_MSC_NO_PREVENT_ALLOW) == 0) { + err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, + &scsi_prevent_removal, sizeof(scsi_prevent_removal), USB_MS_HZ); - } - if (err != 0) { - if (err != ERR_CSW_FAILED) - goto error; - DPRINTF("Device doesn't handle prevent and allow removal\n"); - usbd_add_dynamic_quirk(udev, UQ_MSC_NO_PREVENT_ALLOW); + if (err == 0) { + err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, + &scsi_allow_removal, sizeof(scsi_allow_removal), + USB_MS_HZ); + } + + if (err != 0) { + if (err != ERR_CSW_FAILED) + goto error; + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_PREVENT_ALLOW); + } } timeout = 1; retry_sync_cache: - err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, &scsi_sync_cache, sizeof(scsi_sync_cache), USB_MS_HZ); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; - DPRINTF("Device doesn't handle synchronize cache\n"); - - usbd_add_dynamic_quirk(udev, UQ_MSC_NO_SYNC_CACHE); + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE); } else { /* * Certain Kingston memory sticks fail the first * read capacity after a synchronize cache command * has been issued. Disable the synchronize cache * command for such devices. */ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8, &scsi_read_capacity, sizeof(scsi_read_capacity), USB_MS_HZ); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8, &scsi_read_capacity, sizeof(scsi_read_capacity), USB_MS_HZ); if (err == 0) { if (timeout--) goto retry_sync_cache; - DPRINTF("Device most likely doesn't " - "handle synchronize cache\n"); - - usbd_add_dynamic_quirk(udev, - UQ_MSC_NO_SYNC_CACHE); + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE); } else { if (err != ERR_CSW_FAILED) goto error; } } } + if (usb_test_quirk(uaa, UQ_MSC_NO_START_STOP) == 0) { + err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, + &scsi_start_unit, sizeof(scsi_start_unit), + USB_MS_HZ); + + if (err != 0) { + if (err != ERR_CSW_FAILED) + goto error; + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_START_STOP); + } + } + /* clear sense status of any failed commands on the device */ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), USB_MS_HZ); DPRINTF("Inquiry = %d\n", err); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; } err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, SCSI_SENSE_LEN, &scsi_request_sense, sizeof(scsi_request_sense), USB_MS_HZ); DPRINTF("Request sense = %d\n", err); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; } + goto done; +error: + /* Apply most quirks */ + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE); + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_PREVENT_ALLOW); + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_TEST_UNIT_READY); + USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_START_STOP); done: bbb_detach(sc); - return (0); -error: - bbb_detach(sc); - - DPRINTF("Device did not respond, enabling all quirks\n"); - - usbd_add_dynamic_quirk(udev, UQ_MSC_NO_SYNC_CACHE); - usbd_add_dynamic_quirk(udev, UQ_MSC_NO_PREVENT_ALLOW); - usbd_add_dynamic_quirk(udev, UQ_MSC_NO_TEST_UNIT_READY); + if (any_quirk) { + /* Unconfigure device, to clear software data toggle. */ + usbd_set_config_index(udev, USB_UNCONFIG_INDEX); - /* Need to re-enumerate the device */ - usbd_req_re_enumerate(udev, NULL); + /* Need to re-enumerate the device to clear its state. */ + usbd_req_re_enumerate(udev, NULL); + return (USB_ERR_STALLED); + } - return (USB_ERR_STALLED); + /* No quirks were added, continue as usual. */ + return (0); } usb_error_t usb_msc_eject(struct usb_device *udev, uint8_t iface_index, int method) { struct bbb_transfer *sc; usb_error_t err; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (USB_ERR_INVAL); switch (method) { case MSC_EJECT_STOPUNIT: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_test_unit_ready, sizeof(scsi_test_unit_ready), USB_MS_HZ); DPRINTF("Test unit ready status: %s\n", usbd_errstr(err)); err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, - &scsi_start_stop_unit, sizeof(scsi_start_stop_unit), + &scsi_stop_unit, sizeof(scsi_stop_unit), USB_MS_HZ); break; case MSC_EJECT_REZERO: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_rezero_init, sizeof(scsi_rezero_init), USB_MS_HZ); break; case MSC_EJECT_ZTESTOR: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_ztestor_eject, sizeof(scsi_ztestor_eject), USB_MS_HZ); break; case MSC_EJECT_CMOTECH: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_cmotech_eject, sizeof(scsi_cmotech_eject), USB_MS_HZ); break; case MSC_EJECT_HUAWEI: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_huawei_eject, sizeof(scsi_huawei_eject), USB_MS_HZ); break; case MSC_EJECT_HUAWEI2: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_huawei_eject2, sizeof(scsi_huawei_eject2), USB_MS_HZ); break; case MSC_EJECT_TCT: /* * TCTMobile needs DIR_IN flag. To get it, we * supply a dummy data with the command. */ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, sc->buffer_size, &scsi_tct_eject, sizeof(scsi_tct_eject), USB_MS_HZ); break; default: DPRINTF("Unknown eject method (%d)\n", method); bbb_detach(sc); return (USB_ERR_INVAL); } DPRINTF("Eject CD command status: %s\n", usbd_errstr(err)); bbb_detach(sc); return (0); } usb_error_t usb_dymo_eject(struct usb_device *udev, uint8_t iface_index) { static const uint8_t data[3] = { 0x1b, 0x5a, 0x01 }; struct bbb_transfer *sc; usb_error_t err; sc = bbb_attach(udev, iface_index, UICLASS_HID); if (sc == NULL) return (USB_ERR_INVAL); err = bbb_raw_write(sc, data, sizeof(data), USB_MS_HZ); bbb_detach(sc); return (err); } usb_error_t usb_msc_read_10(struct usb_device *udev, uint8_t iface_index, uint32_t lba, uint32_t blocks, void *buffer) { struct bbb_transfer *sc; uint8_t cmd[10]; usb_error_t err; cmd[0] = 0x28; /* READ_10 */ cmd[1] = 0; cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba >> 0; cmd[6] = 0; cmd[7] = blocks >> 8; cmd[8] = blocks; cmd[9] = 0; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (USB_ERR_INVAL); err = bbb_command_start(sc, DIR_IN, 0, buffer, blocks * SCSI_FIXED_BLOCK_SIZE, cmd, 10, USB_MS_HZ); bbb_detach(sc); return (err); } usb_error_t usb_msc_write_10(struct usb_device *udev, uint8_t iface_index, uint32_t lba, uint32_t blocks, void *buffer) { struct bbb_transfer *sc; uint8_t cmd[10]; usb_error_t err; cmd[0] = 0x2a; /* WRITE_10 */ cmd[1] = 0; cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba >> 0; cmd[6] = 0; cmd[7] = blocks >> 8; cmd[8] = blocks; cmd[9] = 0; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (USB_ERR_INVAL); err = bbb_command_start(sc, DIR_OUT, 0, buffer, blocks * SCSI_FIXED_BLOCK_SIZE, cmd, 10, USB_MS_HZ); bbb_detach(sc); return (err); } usb_error_t usb_msc_read_capacity(struct usb_device *udev, uint8_t iface_index, uint32_t *lba_last, uint32_t *block_size) { struct bbb_transfer *sc; usb_error_t err; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (USB_ERR_INVAL); err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8, &scsi_read_capacity, sizeof(scsi_read_capacity), USB_MS_HZ); *lba_last = (sc->buffer[0] << 24) | (sc->buffer[1] << 16) | (sc->buffer[2] << 8) | (sc->buffer[3]); *block_size = (sc->buffer[4] << 24) | (sc->buffer[5] << 16) | (sc->buffer[6] << 8) | (sc->buffer[7]); /* we currently only support one block size */ if (*block_size != SCSI_FIXED_BLOCK_SIZE) err = USB_ERR_INVAL; bbb_detach(sc); return (err); } diff --git a/sys/dev/usb/usb_msctest.h b/sys/dev/usb/usb_msctest.h index 6b5d3283738b..ba4e094bab60 100644 --- a/sys/dev/usb/usb_msctest.h +++ b/sys/dev/usb/usb_msctest.h @@ -1,60 +1,60 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2008-2022 Hans Petter Selasky. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _USB_MSCTEST_H_ #define _USB_MSCTEST_H_ enum { MSC_EJECT_STOPUNIT, MSC_EJECT_REZERO, MSC_EJECT_ZTESTOR, MSC_EJECT_CMOTECH, MSC_EJECT_HUAWEI, MSC_EJECT_HUAWEI2, MSC_EJECT_TCT, }; int usb_iface_is_cdrom(struct usb_device *udev, uint8_t iface_index); usb_error_t usb_msc_eject(struct usb_device *udev, uint8_t iface_index, int method); usb_error_t usb_msc_auto_quirk(struct usb_device *udev, - uint8_t iface_index); + uint8_t iface_index, const struct usb_attach_arg *uaa); usb_error_t usb_msc_read_10(struct usb_device *udev, uint8_t iface_index, uint32_t lba, uint32_t blocks, void *buffer); usb_error_t usb_msc_write_10(struct usb_device *udev, uint8_t iface_index, uint32_t lba, uint32_t blocks, void *buffer); usb_error_t usb_msc_read_capacity(struct usb_device *udev, uint8_t iface_index, uint32_t *lba_last, uint32_t *block_size); usb_error_t usb_dymo_eject(struct usb_device *udev, uint8_t iface_index); #endif /* _USB_MSCTEST_H_ */