Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F137643934
D8787.id22903.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
34 KB
Referenced Files
None
Subscribers
None
D8787.id22903.diff
View Options
Index: share/man/man4/Makefile
===================================================================
--- share/man/man4/Makefile
+++ share/man/man4/Makefile
@@ -100,6 +100,7 @@
cd.4 \
cdce.4 \
cfi.4 \
+ cfumass.4 \
ch.4 \
ciss.4 \
cloudabi.4 \
Index: share/man/man4/cfumass.4
===================================================================
--- /dev/null
+++ share/man/man4/cfumass.4
@@ -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.
Index: share/man/man4/ctl.4
===================================================================
--- share/man/man4/ctl.4
+++ share/man/man4/ctl.4
@@ -172,6 +172,7 @@
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
+++ share/man/man4/umass.4
@@ -239,6 +239,7 @@
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
+++ share/man/man4/usb_template.4
@@ -84,6 +84,7 @@
.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
+++ share/man/man4/usfs.4
@@ -58,6 +58,7 @@
.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
+++ sys/conf/files
@@ -2929,6 +2929,7 @@
#
# 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
===================================================================
--- /dev/null
+++ sys/dev/usb/storage/cfumass.c
@@ -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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/refcount.h>
+#include <sys/sysctl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+#include "usb_if.h"
+
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_da.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_backend.h>
+#include <cam/ctl/ctl_error.h>
+#include <cam/ctl/ctl_frontend.h>
+#include <cam/ctl/ctl_debug.h>
+#include <cam/ctl/ctl_ha.h>
+#include <cam/ctl/ctl_ioctl.h>
+#include <cam/ctl/ctl_private.h>
+
+#define USB_DEBUG_VAR cfumass_debug
+#include <dev/usb/usb_debug.h>
+#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);
+}
Index: sys/modules/usb/Makefile
===================================================================
--- sys/modules/usb/Makefile
+++ sys/modules/usb/Makefile
@@ -47,7 +47,7 @@
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
===================================================================
--- /dev/null
+++ sys/modules/usb/cfumass/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+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 <bsd.kmod.mk>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Nov 25, 6:26 PM (16 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
26159180
Default Alt Text
D8787.id22903.diff (34 KB)
Attached To
Mode
D8787: Add USB Mass Storage CTL frontend.
Attached
Detach File
Event Timeline
Log In to Comment