Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F137630183
D8787.id25003.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.id25003.diff
View Options
Index: share/man/man4/Makefile
===================================================================
--- share/man/man4/Makefile
+++ share/man/man4/Makefile
@@ -103,6 +103,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,137 @@
+.\" Copyright (c) 2017 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 January 27, 2017
+.Dt CFUMASS 4
+.Os
+.Sh NAME
+.Nm cfumass
+.Nd USB device side support for Mass Storage Class Transport
+.Sh SYNOPSIS
+This driver can be compiled into the kernel by placing these lines in
+the 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
+The driver module can also be loaded at boot by adding this line to
+.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
+To use
+.Nm :
+.Bl -bullet
+.It
+.Xr cfumass 4
+must be loaded as a module or compiled into the kernel.
+.It
+The USB Mass Storage template must be chosen by setting the
+.Va hw.usb.template
+sysctl to 0.
+.It
+The USB OTG port must be working in USB device-side mode.
+This happens automatically upon connection to a USB host.
+.It
+There must be a
+.Xr ctl
+LUN configured for the
+.Pa cfumass
+port.
+.El
+.Pp
+Upon loading, the driver creates a
+.Xr ctl 4
+port named
+.Pa cfumass ,
+presenting the first LUN mapped for that port - usually LUN 0 - to
+the USB host.
+See
+.Xr ctl.conf 5
+and
+.Xr ctld 8
+for details on configuring the LUN.
+.Sh SYSCTL VARIABLES
+These 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 the
+drive is reconnected.
+Set to 0 to handle the command in a standards-compliant way,
+1 to ignore it and log a warning, or 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
@@ -3056,6 +3056,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,1075 @@
+/*-
+ * 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$");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/refcount.h>
+#include <sys/stdint.h>
+#include <sys/sysctl.h>
+#include <sys/systm.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>
+
+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");
+
+/*
+ * The driver uses a single, global CTL port. It could create its ports
+ * in cfumass_attach() instead, but that would make it impossible to specify
+ * "port cfumass0" in ctl.conf(5), as the port generally wouldn't exist
+ * at the time ctld(8) gets run.
+ */
+struct ctl_port cfumass_port;
+bool cfumass_port_online;
+volatile u_int cfumass_refcount;
+
+#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;
+ 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 int cfumass_shutdown(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,
+ .shutdown = cfumass_shutdown,
+};
+CTL_FRONTEND_DECLARE(ctlcfumass, cfumass_frontend);
+
+#define CFUMASS_DEBUG(S, X, ...) \
+ do { \
+ if (debug > 1) { \
+ device_printf(S->sc_dev, "%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;
+ int error;
+
+ sc = device_get_softc(dev);
+ uaa = device_get_ivars(dev);
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+
+ CFUMASS_DEBUG(sc, "go");
+
+ usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE);
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "cfumass", NULL, MTX_DEF);
+ refcount_acquire(&cfumass_refcount);
+
+ 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));
+ refcount_release(&cfumass_refcount);
+ return (ENXIO);
+ }
+
+ 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);
+
+ sc->sc_ctl_initid = ctl_add_initiator(&cfumass_port, -1, 0, NULL);
+ if (sc->sc_ctl_initid < 0) {
+ CFUMASS_WARN(sc, "ctl_add_initiator() failed with error %d",
+ sc->sc_ctl_initid);
+ usbd_transfer_unsetup(sc->sc_xfer, CFUMASS_T_MAX);
+ refcount_release(&cfumass_refcount);
+ return (ENXIO);
+ }
+
+ refcount_init(&sc->sc_queued, 0);
+
+ CFUMASS_LOCK(sc);
+ cfumass_transfer_start(sc, CFUMASS_T_COMMAND);
+ CFUMASS_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+cfumass_detach(device_t dev)
+{
+ struct cfumass_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ CFUMASS_DEBUG(sc, "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(&cfumass_port, sc->sc_ctl_initid);
+ if (error != 0) {
+ CFUMASS_WARN(sc, "ctl_remove_initiator() failed "
+ "with error %d", error);
+ }
+ sc->sc_ctl_initid = -1;
+ }
+
+ mtx_destroy(&sc->sc_mtx);
+ refcount_release(&cfumass_refcount);
+
+ return (0);
+}
+
+static int
+cfumass_suspend(device_t dev)
+{
+ struct cfumass_softc *sc;
+
+ sc = device_get_softc(dev);
+ CFUMASS_DEBUG(sc, "go");
+
+ return (0);
+}
+
+static int
+cfumass_resume(device_t dev)
+{
+ struct cfumass_softc *sc;
+
+ sc = device_get_softc(dev);
+ CFUMASS_DEBUG(sc, "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_DEBUG(sc, "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_DEBUG(sc, "%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_DEBUG(sc, "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_DEBUG(sc, "Bulk-Only Mass Storage Reset done");
+ return (0);
+ }
+
+ if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) &&
+ (req->bRequest == UR_GET_MAX_LUN)) {
+ CFUMASS_DEBUG(sc, "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_DEBUG(sc, "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_DEBUG(sc, "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_DEBUG(sc, "USB_ST_TRANSFERRED");
+
+ signature = UGETDW(sc->sc_cbw->dCBWSignature);
+ if (signature != CBWSIGNATURE) {
+ CFUMASS_WARN(sc, "wrong 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;
+
+ if (!cfumass_port_online) {
+ CFUMASS_DEBUG(sc, "cfumass port is offline; stalling");
+ usbd_xfer_set_stall(xfer);
+ break;
+ }
+
+ /*
+ * Those CTL functions cannot be called with mutex held.
+ */
+ CFUMASS_UNLOCK(sc);
+ io = ctl_alloc_io(cfumass_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 = cfumass_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_DEBUG(sc, "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_DEBUG(sc, "USB_ERR_CANCELLED");
+ break;
+ }
+
+ CFUMASS_DEBUG(sc, "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;
+ struct ctl_sg_entry ctl_sg_entry, *ctl_sglist;
+ int actlen, ctl_sg_count;
+
+ sc = usbd_xfer_softc(xfer);
+
+ CFUMASS_DEBUG(sc, "go");
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ io = sc->sc_ctl_io;
+
+ 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;
+ }
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ CFUMASS_DEBUG(sc, "USB_ST_TRANSFERRED");
+
+ /*
+ * If the host sent less data than required, zero-out
+ * the remaining buffer space, to prevent a malicious host
+ * to writing uninitialized kernel memory to the disk.
+ */
+ if (actlen != ctl_sglist[0].len) {
+ KASSERT(actlen <= ctl_sglist[0].len,
+ ("actlen %zd > ctl_sglist.len %zd",
+ actlen, ctl_sglist[0].len));
+
+ CFUMASS_DEBUG(sc, "host transferred %zd bytes"
+ "instead of expected %zd bytes",
+ actlen, ctl_sglist[0].len);
+
+ memset((char *)(ctl_sglist[0].addr) + actlen, 0,
+ ctl_sglist[0].len - actlen);
+ }
+
+ sc->sc_current_residue = 0;
+ io->scsiio.be_move_done(io);
+ sc->sc_ctl_io = NULL;
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ CFUMASS_DEBUG(sc, "USB_ST_SETUP");
+
+ CFUMASS_DEBUG(sc, "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_DEBUG(sc, "USB_ERR_CANCELLED");
+ break;
+ }
+
+ CFUMASS_DEBUG(sc, "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 ctl_sg_count;
+
+ sc = usbd_xfer_softc(xfer);
+ max_bulk = usbd_xfer_max_len(xfer);
+
+ io = sc->sc_ctl_io;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ CFUMASS_DEBUG(sc, "USB_ST_TRANSFERRED");
+
+ io->scsiio.be_move_done(io);
+ sc->sc_ctl_io = NULL;
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ CFUMASS_DEBUG(sc, "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_DEBUG(sc, "initiator requested %d bytes, "
+ "we will send %ju and stall",
+ sc->sc_current_transfer_length,
+ (uintmax_t)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_DEBUG(sc, "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;
+
+ 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_DEBUG(sc, "USB_ERR_CANCELLED");
+ break;
+ }
+
+ CFUMASS_DEBUG(sc, "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_DEBUG(sc, "USB_ST_TRANSFERRED");
+
+ cfumass_transfer_start(sc, CFUMASS_T_COMMAND);
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ CFUMASS_DEBUG(sc, "USB_ST_SETUP");
+
+ if (sc->sc_current_residue > 0 && !sc->sc_current_stalled) {
+ CFUMASS_DEBUG(sc, "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_DEBUG(sc, "USB_ERR_CANCELLED");
+ break;
+ }
+
+ CFUMASS_DEBUG(sc, "USB_ST_ERROR: %s",
+ usbd_errstr(usb_error));
+
+ goto tr_setup;
+ }
+}
+
+static void
+cfumass_online(void *arg __unused)
+{
+
+ cfumass_port_online = true;
+}
+
+static void
+cfumass_offline(void *arg __unused)
+{
+
+ cfumass_port_online = false;
+}
+
+static void
+cfumass_datamove(union ctl_io *io)
+{
+ struct cfumass_softc *sc;
+
+ sc = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr;
+
+ CFUMASS_DEBUG(sc, "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, "wrong bCBWFlags 0x%x, should be 0x%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, "wrong bCBWFlags 0x%x, should be 0x%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;
+}
+
+static void
+cfumass_done(union ctl_io *io)
+{
+ struct cfumass_softc *sc;
+
+ sc = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr;
+
+ CFUMASS_DEBUG(sc, "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)
+{
+ int error;
+
+ cfumass_port.frontend = &cfumass_frontend;
+ cfumass_port.port_type = CTL_PORT_UMASS;
+ /* XXX KDM what should the real number be here? */
+ cfumass_port.num_requested_ctl_io = 4096;
+ cfumass_port.port_name = "cfumass";
+ cfumass_port.physical_port = 0;
+ cfumass_port.virtual_port = 0;
+ cfumass_port.port_online = cfumass_online;
+ cfumass_port.port_offline = cfumass_offline;
+ cfumass_port.onoff_arg = NULL;
+ cfumass_port.fe_datamove = cfumass_datamove;
+ cfumass_port.fe_done = cfumass_done;
+ cfumass_port.targ_port = -1;
+
+ error = ctl_port_register(&cfumass_port);
+ if (error != 0) {
+ printf("%s: ctl_port_register() failed "
+ "with error %d", __func__, error);
+ }
+
+ cfumass_port_online = true;
+ refcount_init(&cfumass_refcount, 0);
+
+ return (error);
+}
+
+int
+cfumass_shutdown(void)
+{
+ int error;
+
+ if (cfumass_refcount > 0) {
+ if (debug > 1) {
+ printf("%s: still have %u attachments; "
+ "returning EBUSY\n", __func__, cfumass_refcount);
+ }
+ return (EBUSY);
+ }
+
+ error = ctl_port_deregister(&cfumass_port);
+ if (error != 0) {
+ printf("%s: ctl_port_deregister() failed "
+ "with error %d\n", __func__, error);
+ }
+
+ return (error);
+}
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,9 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../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, 4:50 PM (14 h, 32 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
26151985
Default Alt Text
D8787.id25003.diff (34 KB)
Attached To
Mode
D8787: Add USB Mass Storage CTL frontend.
Attached
Detach File
Event Timeline
Log In to Comment