Index: share/man/man4/Makefile =================================================================== --- share/man/man4/Makefile (revision 309186) +++ share/man/man4/Makefile (working copy) @@ -103,6 +103,7 @@ MAN= aac.4 \ cd.4 \ cdce.4 \ cfi.4 \ + cfumass.4 \ ch.4 \ ciss.4 \ cloudabi.4 \ Index: share/man/man4/cfumass.4 =================================================================== --- share/man/man4/cfumass.4 (nonexistent) +++ share/man/man4/cfumass.4 (working copy) @@ -0,0 +1,123 @@ +.\" Copyright (c) 2016 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" 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$ +.Dd June 8, 2016 +.Dt CFUMASS 4 +.Os +.Sh NAME +.Nm cfumass +.Nd USB device side support for Mass Storage Class Bulk-Only (BBB) Transport +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb" +.Cd "device usb_template" +.Cd "device iscsi" +.Cd "device ctl" +.Cd "device cfumass" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +cfumass_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides device side support for emulating an USB mass storage +device compliant with the USB Mass Storage Class Bulk-Only (BBB) Transport +specification, implemented as a +.Xr ctl 4 +frontend driver. +.Pp +In order to use this functionality, the +.Xr usb_template 4 +driver needs to be loaded, the USB OTG port must be working in a +USB device side mode (which happens automatically upon connecting to a +USB host), and the USB Mass Storage template needs to be chosen by +setting the +.Va hw.usb.template +sysctl to 0. +.Pp +Upon attach the driver creates a ctl(4) port named "umassX", where "X" +is the USB interface number, usually 0, presenting LUN 0 to the USB host. +See +.Xr ctl.conf 5 +and +.Xr ctld 8 +for details on configuring the LUN. +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.usb.cfumass.debug +Verbosity level for log messages from the +.Nm +driver. +Set to 0 to disable logging or 1 to warn about potential problems. +Larger values enable debugging output. +Defaults to 1. +.It Va hw.usb.cfumass.ignore_stop +Ignore START STOP UNIT SCSI commands with START and LOEJ bits cleared. +Some initiators send that command to stop the target when the user +attempts to gracefully eject the drive, but fail to start it when it's +plugged back. +Set to 0 to handle the command in a standards-compliant way, +1 to ignore it and log a warning, and 2 to ignore it silently. +Defaults to 1. +.It Va hw.usb.cfumass.max_lun +Max LUN number to report to the initiator (USB host). +Must be between 0 and 15. +Some initiators incorrectly handle values larger than 0. +Defaults to 0. +.El +.Sh SEE ALSO +.Xr ctl 4 , +.Xr umass 4 , +.Xr usb 4 , +.Xr usb_template 4 , +.Xr ctl.conf 5 , +.Xr ctld 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 11.1 . +.Sh AUTHORS +The +.Nm +driver was developed by +.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. Property changes on: share/man/man4/cfumass.4 ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: share/man/man4/ctl.4 =================================================================== --- share/man/man4/ctl.4 (revision 309186) +++ share/man/man4/ctl.4 (working copy) @@ -172,6 +172,7 @@ Set to 0 to disable sending NOP-In PDUs. Defaults to 5. .El .Sh SEE ALSO +.Xr cfumass 4 , .Xr ctladm 8 , .Xr ctld 8 , .Xr ctlstat 8 Index: share/man/man4/umass.4 =================================================================== --- share/man/man4/umass.4 (revision 309186) +++ share/man/man4/umass.4 (working copy) @@ -239,6 +239,7 @@ type as when using .Xr mount 8 . .Sh SEE ALSO +.Xr cfumass 4 , .Xr ehci 4 , .Xr ohci 4 , .Xr uhci 4 , Index: share/man/man4/usb_template.4 =================================================================== --- share/man/man4/usb_template.4 (revision 309186) +++ share/man/man4/usb_template.4 (working copy) @@ -84,6 +84,7 @@ Available templates are: .El . .Sh SEE ALSO +.Xr cfumass 4 , .Xr usb 4 , .Xr usfs 4 .Sh STANDARDS Index: share/man/man4/usfs.4 =================================================================== --- share/man/man4/usfs.4 (revision 309186) +++ share/man/man4/usfs.4 (working copy) @@ -58,6 +58,7 @@ sysctl is set to 0). .Pp Upon attach the driver creates a RAM disk which can be read and written. .Sh SEE ALSO +.Xr cfumass 4 , .Xr umass 4 , .Xr usb 4 , .Xr usb_template 4 Index: sys/conf/files =================================================================== --- sys/conf/files (revision 309186) +++ sys/conf/files (working copy) @@ -2978,6 +2978,7 @@ dev/usb/controller/usb_controller.c optional usb # # USB storage drivers # +dev/usb/storage/cfumass.c optional cfumass ctl dev/usb/storage/umass.c optional umass dev/usb/storage/urio.c optional urio dev/usb/storage/ustorage_fs.c optional usfs Index: sys/dev/usb/storage/cfumass.c =================================================================== --- sys/dev/usb/storage/cfumass.c (nonexistent) +++ sys/dev/usb/storage/cfumass.c (working copy) @@ -0,0 +1,1059 @@ +/*- + * Copyright (c) 2016 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * 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. + * + */ +/* + * USB Mass Storage Class Bulk-Only (BBB) Transport target. + * + * http://www.usb.org/developers/docs/devclass_docs/usbmassbulk_10.pdf + * + * This code implements the USB Mass Storage frontend driver for the CAM + * Target Layer (ctl(4)) subsystem. + */ + +#include +__FBSDID("$FreeBSD$"); + +#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 "usbdevs.h" +#include "usb_if.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR cfumass_debug +#include +#endif /* USB_GLOBAL_INCLUDE_FILE */ + +SYSCTL_NODE(_hw_usb, OID_AUTO, cfumass, CTLFLAG_RW, 0, + "CAM Target Layer USB Mass Storage Frontend"); +static int debug = 1; +SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, debug, CTLFLAG_RWTUN, + &debug, 1, "Enable debug messages"); +static int max_lun = 0; +SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, max_lun, CTLFLAG_RWTUN, + &max_lun, 1, "Maximum advertised LUN number"); +static int ignore_stop = 1; +SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, ignore_stop, CTLFLAG_RWTUN, + &ignore_stop, 1, "Ignore START STOP UNIT with START and LOEJ bits cleared"); + +#ifndef CFUMASS_BULK_SIZE +#define CFUMASS_BULK_SIZE (1U << 17) /* bytes */ +#endif + +/* + * USB transfer definitions. + */ +#define CFUMASS_T_COMMAND 0 +#define CFUMASS_T_DATA_OUT 1 +#define CFUMASS_T_DATA_IN 2 +#define CFUMASS_T_STATUS 3 +#define CFUMASS_T_MAX 4 + +/* + * USB interface specific control requests. + */ +#define UR_RESET 0xff /* Bulk-Only Mass Storage Reset */ +#define UR_GET_MAX_LUN 0xfe /* Get Max LUN */ + +/* + * Command Block Wrapper. + */ +struct cfumass_cbw_t { + uDWord dCBWSignature; +#define CBWSIGNATURE 0x43425355 /* "USBC" */ + uDWord dCBWTag; + uDWord dCBWDataTransferLength; + uByte bCBWFlags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + uByte bCBWLUN; + uByte bCDBLength; +#define CBWCBLENGTH 16 + uByte CBWCB[CBWCBLENGTH]; +} __packed; + +#define CFUMASS_CBW_SIZE 31 +CTASSERT(sizeof(struct cfumass_cbw_t) == CFUMASS_CBW_SIZE); + +/* + * Command Status Wrapper. + */ +struct cfumass_csw_t { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 /* "USBS" */ + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed; + +#define CFUMASS_CSW_SIZE 13 +CTASSERT(sizeof(struct cfumass_csw_t) == CFUMASS_CSW_SIZE); + +struct cfumass_softc { + device_t sc_dev; + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[CFUMASS_T_MAX]; + + struct cfumass_cbw_t *sc_cbw; + struct cfumass_csw_t *sc_csw; + + struct mtx sc_mtx; + struct ctl_port sc_port; + int sc_online; + int sc_ctl_initid; + + /* + * This is used to communicate between CTL callbacks + * and USB callbacks; basically, it holds the state + * for the current command ("the" command, since there + * is no queueing in USB Mass Storage). + */ + bool sc_current_stalled; + + /* + * The following are set upon receiving a SCSI command. + */ + int sc_current_tag; + int sc_current_transfer_length; + int sc_current_flags; + + /* + * The following are set in ctl_datamove(). + */ + int sc_current_residue; + union ctl_io *sc_ctl_io; + + /* + * The following is set in cfumass_done(). + */ + int sc_current_status; + + /* + * Number of requests queued to CTL. + */ + volatile u_int sc_queued; +}; + +/* + * USB interface. + */ +static device_probe_t cfumass_probe; +static device_attach_t cfumass_attach; +static device_detach_t cfumass_detach; +static device_suspend_t cfumass_suspend; +static device_resume_t cfumass_resume; +static usb_handle_request_t cfumass_handle_request; + +static usb_callback_t cfumass_t_command_callback; +static usb_callback_t cfumass_t_data_out_callback; +static usb_callback_t cfumass_t_data_in_callback; +static usb_callback_t cfumass_t_status_callback; + +static device_method_t cfumass_methods[] = { + + /* USB interface. */ + DEVMETHOD(usb_handle_request, cfumass_handle_request), + + /* Device interface. */ + DEVMETHOD(device_probe, cfumass_probe), + DEVMETHOD(device_attach, cfumass_attach), + DEVMETHOD(device_detach, cfumass_detach), + DEVMETHOD(device_suspend, cfumass_suspend), + DEVMETHOD(device_resume, cfumass_resume), + + DEVMETHOD_END +}; + +static driver_t cfumass_driver = { + .name = "cfumass", + .methods = cfumass_methods, + .size = sizeof(struct cfumass_softc), +}; + +static devclass_t cfumass_devclass; + +DRIVER_MODULE(cfumass, uhub, cfumass_driver, cfumass_devclass, NULL, 0); +MODULE_VERSION(cfumass, 0); +MODULE_DEPEND(cfumass, usb, 1, 1, 1); +MODULE_DEPEND(cfumass, usb_template, 1, 1, 1); + +static struct usb_config cfumass_config[CFUMASS_T_MAX] = { + + [CFUMASS_T_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = sizeof(struct cfumass_cbw_t), + .callback = &cfumass_t_command_callback, + .usb_mode = USB_MODE_DEVICE, + }, + + [CFUMASS_T_DATA_OUT] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = CFUMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1, .short_xfer_ok = 1, .ext_buffer = 1}, + .callback = &cfumass_t_data_out_callback, + .usb_mode = USB_MODE_DEVICE, + }, + + [CFUMASS_T_DATA_IN] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = CFUMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1, .short_xfer_ok = 1, .ext_buffer = 1}, + .callback = &cfumass_t_data_in_callback, + .usb_mode = USB_MODE_DEVICE, + }, + + [CFUMASS_T_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = sizeof(struct cfumass_csw_t), + .flags = {.short_xfer_ok = 1}, + .callback = &cfumass_t_status_callback, + .usb_mode = USB_MODE_DEVICE, + }, +}; + +/* + * CTL frontend interface. + */ +static int cfumass_init(void); +static void cfumass_online(void *arg); +static void cfumass_offline(void *arg); +static void cfumass_datamove(union ctl_io *io); +static void cfumass_done(union ctl_io *io); + +static struct ctl_frontend cfumass_frontend = { + .name = "umass", + .init = cfumass_init, +}; +CTL_FRONTEND_DECLARE(ctlcfumass, cfumass_frontend); + +#define CFUMASS_DEV_DEBUG(D, X, ...) \ + do { \ + if (debug > 1) { \ + device_printf(D, "%s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } \ + } while (0) + +#define CFUMASS_WARN(S, X, ...) \ + do { \ + if (debug > 0) { \ + device_printf(S->sc_dev, "WARNING: %s: " X "\n",\ + __func__, ## __VA_ARGS__); \ + } \ + } while (0) + +#define CFUMASS_LOCK(X) mtx_lock(&X->sc_mtx) +#define CFUMASS_UNLOCK(X) mtx_unlock(&X->sc_mtx) + +static void cfumass_transfer_start(struct cfumass_softc *sc, uint8_t xfer_index); +static void cfumass_terminate(struct cfumass_softc *sc); + +static int +cfumass_probe(device_t dev) +{ + struct usb_attach_arg *uaa; + struct usb_interface_descriptor *id; + + uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_DEVICE) + return (ENXIO); + + /* + * Check for a compliant device. + */ + id = usbd_get_interface_descriptor(uaa->iface); + if ((id == NULL) || + (id->bInterfaceClass != UICLASS_MASS) || + (id->bInterfaceSubClass != UISUBCLASS_SCSI) || + (id->bInterfaceProtocol != UIPROTO_MASS_BBB)) { + return (ENXIO); + } + + return (BUS_PROBE_GENERIC); +} + +static int +cfumass_attach(device_t dev) +{ + struct cfumass_softc *sc; + struct usb_attach_arg *uaa; + struct usb_interface_descriptor *id; + struct ctl_port *port; + int error; + + /* + * NOTE: the softc struct is cleared in device_set_driver. + * We can safely call cfumass_detach without specifically + * initializing the struct. + */ + + CFUMASS_DEV_DEBUG(dev, "go"); + + sc = device_get_softc(dev); + uaa = device_get_ivars(dev); + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + + usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE); + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "cfumass", NULL, MTX_DEF); + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) { + CFUMASS_WARN(sc, "usbd_get_interface_descriptor() failed"); + goto detach; + } + + error = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, cfumass_config, + CFUMASS_T_MAX, sc, &sc->sc_mtx); + if (error != 0) { + CFUMASS_WARN(sc, "usbd_transfer_setup() failed: %s", + usbd_errstr(error)); + goto detach; + } + + sc->sc_cbw = usbd_xfer_get_frame_buffer(sc->sc_xfer[ + CFUMASS_T_COMMAND], 0); + sc->sc_csw = usbd_xfer_get_frame_buffer(sc->sc_xfer[ + CFUMASS_T_STATUS], 0); + + port = &sc->sc_port; + + port->frontend = &cfumass_frontend; + port->port_type = CTL_PORT_ISCSI; // XXX: CTL_PORT_UMASS? + /* XXX KDM what should the real number be here? */ + port->num_requested_ctl_io = 4096; + port->port_name = "cfumass"; + port->physical_port = device_get_unit(dev); + port->virtual_port = 0; + port->port_online = cfumass_online; + port->port_offline = cfumass_offline; + port->onoff_arg = sc; + port->fe_datamove = cfumass_datamove; + port->fe_done = cfumass_done; + + /* XXX KDM what should we report here? */ + /* XXX These should probably be fetched from CTL. */ + port->max_targets = 1; + port->max_target_id = 15; + port->targ_port = -1; + + error = ctl_port_register(port); + if (error != 0) { + CFUMASS_WARN(sc, "ctl_port_register() failed with error %d", error); + return (error); + } + + sc->sc_ctl_initid = ctl_add_initiator(&sc->sc_port, -1, 0, NULL); + if (sc->sc_ctl_initid < 0) { + CFUMASS_WARN(sc, "ctl_add_initiator() failed with error %d", sc->sc_ctl_initid); + return (1); + } + + refcount_init(&sc->sc_queued, 0); + + CFUMASS_LOCK(sc); + cfumass_transfer_start(sc, CFUMASS_T_COMMAND); + CFUMASS_UNLOCK(sc); + + return (0); + +detach: + cfumass_detach(dev); + return (ENXIO); +} + +static int +cfumass_detach(device_t dev) +{ + struct cfumass_softc *sc; + int error; + + sc = device_get_softc(dev); + + CFUMASS_DEV_DEBUG(dev, "go"); + + CFUMASS_LOCK(sc); + cfumass_terminate(sc); + CFUMASS_UNLOCK(sc); + usbd_transfer_unsetup(sc->sc_xfer, CFUMASS_T_MAX); + + if (sc->sc_ctl_initid != -1) { + error = ctl_remove_initiator(&sc->sc_port, sc->sc_ctl_initid); + if (error != 0) + CFUMASS_WARN(sc, "ctl_remove_initiator() failed with error %d", error); + sc->sc_ctl_initid = -1; + } + + // XXX: What if it's not initialized yet? + error = ctl_port_deregister(&sc->sc_port); + if (error != 0) + CFUMASS_WARN(sc, "ctl_port_deregister() failed with error %d", error); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static int +cfumass_suspend(device_t dev) +{ + + CFUMASS_DEV_DEBUG(dev, "go"); + return (0); +} + +static int +cfumass_resume(device_t dev) +{ + + CFUMASS_DEV_DEBUG(dev, "go"); + return (0); +} + +static void +cfumass_transfer_start(struct cfumass_softc *sc, uint8_t xfer_index) +{ + + usbd_transfer_start(sc->sc_xfer[xfer_index]); +} + +static void +cfumass_transfer_stop_and_drain(struct cfumass_softc *sc, uint8_t xfer_index) +{ + + usbd_transfer_stop(sc->sc_xfer[xfer_index]); + CFUMASS_UNLOCK(sc); + usbd_transfer_drain(sc->sc_xfer[xfer_index]); + CFUMASS_LOCK(sc); +} + +static void +cfumass_terminate(struct cfumass_softc *sc) +{ + int last; + + for (;;) { + cfumass_transfer_stop_and_drain(sc, CFUMASS_T_COMMAND); + cfumass_transfer_stop_and_drain(sc, CFUMASS_T_DATA_IN); + cfumass_transfer_stop_and_drain(sc, CFUMASS_T_DATA_OUT); + + if (sc->sc_ctl_io != NULL) { + CFUMASS_DEV_DEBUG(sc->sc_dev, "terminating CTL transfer"); + ctl_set_data_phase_error(&sc->sc_ctl_io->scsiio); + sc->sc_ctl_io->scsiio.be_move_done(sc->sc_ctl_io); + sc->sc_ctl_io = NULL; + } + + cfumass_transfer_stop_and_drain(sc, CFUMASS_T_STATUS); + + refcount_acquire(&sc->sc_queued); + last = refcount_release(&sc->sc_queued); + if (last != 0) + break; + + CFUMASS_DEV_DEBUG(sc->sc_dev, "%d CTL tasks pending", sc->sc_queued); + msleep(__DEVOLATILE(void *, &sc->sc_queued), &sc->sc_mtx, 0, "cfumass_reset", hz / 100); + } +} + +static int +cfumass_handle_request(device_t dev, + const void *preq, void **pptr, uint16_t *plen, + uint16_t offset, uint8_t *pstate) +{ + static uint8_t max_lun_tmp; + struct cfumass_softc *sc; + const struct usb_device_request *req; + uint8_t is_complete; + + sc = device_get_softc(dev); + req = preq; + is_complete = *pstate; + + CFUMASS_DEV_DEBUG(sc->sc_dev, "go"); + + if (is_complete) + return (ENXIO); + + if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && + (req->bRequest == UR_RESET)) { + CFUMASS_WARN(sc, "received Bulk-Only Mass Storage Reset"); + *plen = 0; + + CFUMASS_LOCK(sc); + cfumass_terminate(sc); + cfumass_transfer_start(sc, CFUMASS_T_COMMAND); + CFUMASS_UNLOCK(sc); + + CFUMASS_DEV_DEBUG(sc->sc_dev, "Bulk-Only Mass Storage Reset done"); + return (0); + } + + if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) && + (req->bRequest == UR_GET_MAX_LUN)) { + CFUMASS_DEV_DEBUG(sc->sc_dev, "received Get Max LUN"); + if (offset == 0) { + *plen = 1; + /* + * The protocol doesn't support LUN numbers higher + * than 15. Also, some initiators (namely Windows XP + * SP3 Version 2002) can't properly query the number + * of LUNs, resulting in inaccessible "fake" ones - thus + * the default limit of one LUN. + */ + if (max_lun < 0 || max_lun > 15) { + CFUMASS_WARN(sc, + "invalid hw.usb.cfumass.max_lun, must be " + "between 0 and 15; defaulting to 0"); + max_lun_tmp = 0; + } else { + max_lun_tmp = max_lun; + } + *pptr = &max_lun_tmp; + } else { + *plen = 0; + } + return (0); + } + + return (ENXIO); +} + +static int +cfumass_quirk(struct cfumass_softc *sc, unsigned char *cdb, int cdb_len) +{ + struct scsi_start_stop_unit *sssu; + + switch (cdb[0]) { + case START_STOP_UNIT: + /* + * Some initiators - eg OSX, Darwin Kernel Version 15.6.0, + * root:xnu-3248.60.11~2/RELEASE_X86_64 - attempt to stop + * the unit on eject, but fail to start it when it's plugged + * back. Just ignore the command. + */ + + if (cdb_len < sizeof(*sssu)) { + CFUMASS_DEV_DEBUG(sc->sc_dev, + "received START STOP UNIT with " + "bCDBLength %d, should be %zd", + cdb_len, sizeof(*sssu)); + break; + } + + sssu = (struct scsi_start_stop_unit *)cdb; + if ((sssu->how & SSS_PC_MASK) != 0) + break; + + if ((sssu->how & SSS_START) != 0) + break; + + if ((sssu->how & SSS_LOEJ) != 0) + break; + + if (ignore_stop == 0) + break; + else if (ignore_stop == 1) + CFUMASS_WARN(sc, "ignoring START STOP UNIT request"); + else + CFUMASS_DEV_DEBUG(sc->sc_dev, "ignoring START STOP UNIT request"); + + sc->sc_current_status = 0; + cfumass_transfer_start(sc, CFUMASS_T_STATUS); + + return (1); + default: + break; + } + + return (0); +} + +static void +cfumass_t_command_callback(struct usb_xfer *xfer, usb_error_t usb_error) +{ + struct cfumass_softc *sc; + uint32_t signature; + union ctl_io *io; + int error = 0; + + sc = usbd_xfer_softc(xfer); + + KASSERT(sc->sc_ctl_io == NULL, + ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_TRANSFERRED"); + + signature = UGETDW(sc->sc_cbw->dCBWSignature); + if (signature != CBWSIGNATURE) { + CFUMASS_WARN(sc, "invalid dCBWSignature 0x%08x, should be 0x%08x", + signature, CBWSIGNATURE); + break; + } + + if (sc->sc_cbw->bCDBLength <= 0 || + sc->sc_cbw->bCDBLength > sizeof(sc->sc_cbw->CBWCB)) { + CFUMASS_WARN(sc, "invalid bCDBLength %d, should be <= %zd", + sc->sc_cbw->bCDBLength, sizeof(sc->sc_cbw->CBWCB)); + break; + } + + sc->sc_current_stalled = false; + sc->sc_current_status = 0; + sc->sc_current_tag = UGETDW(sc->sc_cbw->dCBWTag); + sc->sc_current_transfer_length = + UGETDW(sc->sc_cbw->dCBWDataTransferLength); + sc->sc_current_flags = sc->sc_cbw->bCBWFlags; + + /* + * Make sure to report proper residue if the datamove wasn't + * required, or wasn't called due to SCSI error. + */ + sc->sc_current_residue = sc->sc_current_transfer_length; + + if (cfumass_quirk(sc, + sc->sc_cbw->CBWCB, sc->sc_cbw->bCDBLength) != 0) + break; + + /* + * Those CTL functions cannot be called with mutex held. + */ + CFUMASS_UNLOCK(sc); + io = ctl_alloc_io(sc->sc_port.ctl_pool_ref); + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = sc; + io->io_hdr.io_type = CTL_IO_SCSI; + io->io_hdr.nexus.initid = sc->sc_ctl_initid; + io->io_hdr.nexus.targ_port = sc->sc_port.targ_port; + io->io_hdr.nexus.targ_lun = ctl_decode_lun(sc->sc_cbw->bCBWLUN); + io->scsiio.tag_num = UGETDW(sc->sc_cbw->dCBWTag); + io->scsiio.tag_type = CTL_TAG_UNTAGGED; + io->scsiio.cdb_len = sc->sc_cbw->bCDBLength; + memcpy(io->scsiio.cdb, sc->sc_cbw->CBWCB, sc->sc_cbw->bCDBLength); + refcount_acquire(&sc->sc_queued); + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFUMASS_WARN(sc, + "ctl_queue() failed; error %d; stalling", error); + ctl_free_io(io); + refcount_release(&sc->sc_queued); + CFUMASS_LOCK(sc); + usbd_xfer_set_stall(xfer); + break; + } + + CFUMASS_LOCK(sc); + break; + + case USB_ST_SETUP: +tr_setup: + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_SETUP"); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(*sc->sc_cbw)); + usbd_transfer_submit(xfer); + break; + + default: + if (usb_error == USB_ERR_CANCELLED) { + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ERR_CANCELLED"); + break; + } + + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); + + goto tr_setup; + } +} + +static void +cfumass_t_data_out_callback(struct usb_xfer *xfer, usb_error_t usb_error) +{ + struct cfumass_softc *sc; + union ctl_io *io; + uint32_t max_bulk; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + int actlen, ctl_sg_count, sumlen; + + sc = usbd_xfer_softc(xfer); + max_bulk = usbd_xfer_max_len(xfer); + + CFUMASS_DEV_DEBUG(sc->sc_dev, "go"); + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + io = sc->sc_ctl_io; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_TRANSFERRED"); + + sc->sc_current_residue = 0; + io->scsiio.be_move_done(io); + sc->sc_ctl_io = NULL; + break; + + case USB_ST_SETUP: +tr_setup: + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_SETUP"); + + if (io->scsiio.kern_sg_entries > 0) { + ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; + ctl_sg_count = io->scsiio.kern_sg_entries; + } else { + ctl_sglist = &ctl_sg_entry; + ctl_sglist->addr = io->scsiio.kern_data_ptr; + ctl_sglist->len = io->scsiio.kern_data_len; + ctl_sg_count = 1; + } + + CFUMASS_DEV_DEBUG(sc->sc_dev, "requested size %d, CTL segment size %zd", + sc->sc_current_transfer_length, ctl_sglist[0].len); + + usbd_xfer_set_frame_data(xfer, 0, ctl_sglist[0].addr, ctl_sglist[0].len); + usbd_transfer_submit(xfer); + break; + + default: + if (usb_error == USB_ERR_CANCELLED) { + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ERR_CANCELLED"); + break; + } + + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); + + goto tr_setup; + } +} + +static void +cfumass_t_data_in_callback(struct usb_xfer *xfer, usb_error_t usb_error) +{ + struct cfumass_softc *sc; + union ctl_io *io; + uint32_t max_bulk; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + int actlen, ctl_sg_count, sumlen; + + sc = usbd_xfer_softc(xfer); + max_bulk = usbd_xfer_max_len(xfer); + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + io = sc->sc_ctl_io; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_TRANSFERRED"); + + io->scsiio.be_move_done(io); + sc->sc_ctl_io = NULL; + break; + + case USB_ST_SETUP: +tr_setup: + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_SETUP"); + + if (io->scsiio.kern_sg_entries > 0) { + ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; + ctl_sg_count = io->scsiio.kern_sg_entries; + } else { + ctl_sglist = &ctl_sg_entry; + ctl_sglist->addr = io->scsiio.kern_data_ptr; + ctl_sglist->len = io->scsiio.kern_data_len; + ctl_sg_count = 1; + } + + if (sc->sc_current_transfer_length > io->scsiio.kern_total_len) { + CFUMASS_DEV_DEBUG(sc->sc_dev, "initiator requested %zd bytes, we will send %zd and stall", + sc->sc_current_transfer_length, io->scsiio.kern_total_len); + sc->sc_current_residue = sc->sc_current_transfer_length - io->scsiio.kern_total_len; + } else { + sc->sc_current_residue = 0; + } + + CFUMASS_DEV_DEBUG(sc->sc_dev, "max_bulk %d, requested size %d, CTL segment size %zd", + max_bulk, sc->sc_current_transfer_length, ctl_sglist[0].len); + + if (max_bulk >= ctl_sglist[0].len) + max_bulk = ctl_sglist[0].len; + +#if 0 + if (sc->sc_current_residue != 0) { + sc->sc_current_stalled = true; + usbd_xfer_set_stall(xfer); + } +#endif + + usbd_xfer_set_frame_data(xfer, 0, ctl_sglist[0].addr, max_bulk); + usbd_transfer_submit(xfer); + + break; + + default: + if (usb_error == USB_ERR_CANCELLED) { + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ERR_CANCELLED"); + break; + } + + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); + + goto tr_setup; + } +} + +static void +cfumass_t_status_callback(struct usb_xfer *xfer, usb_error_t usb_error) +{ + struct cfumass_softc *sc; + + sc = usbd_xfer_softc(xfer); + + KASSERT(sc->sc_ctl_io == NULL, + ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_TRANSFERRED"); + + cfumass_transfer_start(sc, CFUMASS_T_COMMAND); + break; + + case USB_ST_SETUP: +tr_setup: + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_SETUP"); + + if (sc->sc_current_residue > 0 && !sc->sc_current_stalled) { + CFUMASS_DEV_DEBUG(sc->sc_dev, "non-zero residue, stalling"); + usbd_xfer_set_stall(xfer); + sc->sc_current_stalled = true; + } + + USETDW(sc->sc_csw->dCSWSignature, CSWSIGNATURE); + USETDW(sc->sc_csw->dCSWTag, sc->sc_current_tag); + USETDW(sc->sc_csw->dCSWDataResidue, sc->sc_current_residue); + sc->sc_csw->bCSWStatus = sc->sc_current_status; + + usbd_xfer_set_frame_len(xfer, 0, sizeof(*sc->sc_csw)); + usbd_transfer_submit(xfer); + break; + + default: + if (usb_error == USB_ERR_CANCELLED) { + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ERR_CANCELLED"); + break; + } + + CFUMASS_DEV_DEBUG(sc->sc_dev, "USB_ST_ERROR: %s", usbd_errstr(usb_error)); + + goto tr_setup; + } +} + +static void +cfumass_online(void *arg) +{ + struct cfumass_softc *sc; + + sc = (struct cfumass_softc *)arg; + + CFUMASS_DEV_DEBUG(sc->sc_dev, "go"); + + CFUMASS_LOCK(sc); + sc->sc_online = 1; + CFUMASS_UNLOCK(sc); +} + +static void +cfumass_offline(void *arg) +{ + struct cfumass_softc *sc; + + sc = (struct cfumass_softc *)arg; + + CFUMASS_DEV_DEBUG(sc->sc_dev, "go"); + + CFUMASS_LOCK(sc); + sc->sc_online = 0; + CFUMASS_UNLOCK(sc); +} + +static void +cfumass_datamove(union ctl_io *io) +{ + struct cfumass_softc *sc; + + sc = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + + CFUMASS_DEV_DEBUG(sc->sc_dev, "go"); + + CFUMASS_LOCK(sc); + + KASSERT(sc->sc_ctl_io == NULL, + ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io)); + sc->sc_ctl_io = io; + + if ((io->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) { + /* + * Verify that CTL wants us to send the data in the direction + * expected by the initiator. + */ + if (sc->sc_current_flags != CBWFLAGS_IN) { + CFUMASS_WARN(sc, "invalid bCBWFlags 0x%x, should be x0%x", + sc->sc_current_flags, CBWFLAGS_IN); + goto fail; + } + + cfumass_transfer_start(sc, CFUMASS_T_DATA_IN); + } else { + if (sc->sc_current_flags != CBWFLAGS_OUT) { + CFUMASS_WARN(sc, "invalid bCBWFlags 0x%x, should be x0%x", + sc->sc_current_flags, CBWFLAGS_OUT); + goto fail; + } + + /* We hadn't received anything during this datamove yet. */ + io->scsiio.ext_data_filled = 0; + cfumass_transfer_start(sc, CFUMASS_T_DATA_OUT); + } + + CFUMASS_UNLOCK(sc); + return; + +fail: + ctl_set_data_phase_error(&io->scsiio); + io->scsiio.be_move_done(io); + sc->sc_ctl_io = NULL; + + // XXX: stall? +} + +static void +cfumass_done(union ctl_io *io) +{ + struct cfumass_softc *sc; + + sc = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + + CFUMASS_DEV_DEBUG(sc->sc_dev, "go"); + + KASSERT(((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE), + ("invalid CTL status %#x", io->io_hdr.status)); + KASSERT(sc->sc_ctl_io == NULL, + ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io)); + + if (io->io_hdr.io_type == CTL_IO_TASK && + io->taskio.task_action == CTL_TASK_I_T_NEXUS_RESET) { + /* + * Implicit task termination has just completed; nothing to do. + */ + ctl_free_io(io); + return; + } + + /* + * Do not return status for aborted commands. + * There are exceptions, but none supported by CTL yet. + */ + if (((io->io_hdr.flags & CTL_FLAG_ABORT) && + (io->io_hdr.flags & CTL_FLAG_ABORT_STATUS) == 0) || + (io->io_hdr.flags & CTL_FLAG_STATUS_SENT)) { + ctl_free_io(io); + return; + } + + switch (io->scsiio.scsi_status) { + case SCSI_STATUS_OK: + sc->sc_current_status = 0; + break; + default: + sc->sc_current_status = 1; + break; + } + + CFUMASS_LOCK(sc); + cfumass_transfer_start(sc, CFUMASS_T_STATUS); + CFUMASS_UNLOCK(sc); + ctl_free_io(io); + + refcount_release(&sc->sc_queued); +} + +int +cfumass_init(void) +{ + + /* + * Not much to do here; all the interesting stuff happens in cfumass_attach(). + */ + + return (0); +} Property changes on: sys/dev/usb/storage/cfumass.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: sys/modules/usb/Makefile =================================================================== --- sys/modules/usb/Makefile (revision 309186) +++ sys/modules/usb/Makefile (working copy) @@ -47,7 +47,7 @@ SUBDIR = usb SUBDIR += ${_dwc_otg} ehci ${_musb} ohci uhci xhci ${_uss820dci} ${_at91dci} \ ${_atmegadci} ${_avr32dci} ${_rsu} ${_rsufw} ${_saf1761otg} SUBDIR += ${_rum} ${_run} ${_runfw} ${_uath} upgt usie ural ${_zyd} ${_urtw} -SUBDIR += atp uhid ukbd ums udbp ufm uep wsp ugold uled +SUBDIR += atp cfumass uhid ukbd ums udbp ufm uep wsp ugold uled SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom ufoma uftdi ugensa uipaq ulpt \ umct umcs umodem umoscom uplcom uslcom uvisor uvscom SUBDIR += udl Index: sys/modules/usb/cfumass/Makefile =================================================================== --- sys/modules/usb/cfumass/Makefile (nonexistent) +++ sys/modules/usb/cfumass/Makefile (working copy) @@ -0,0 +1,36 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb/storage + +KMOD= cfumass +SRCS= bus_if.h device_if.h opt_bus.h opt_usb.h usb_if.h usbdevs.h \ + cfumass.c + +.include Property changes on: sys/modules/usb/cfumass/Makefile ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property