summaryrefslogtreecommitdiffstats
path: root/drivers/usb/storage/transport.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/storage/transport.c')
-rw-r--r--drivers/usb/storage/transport.c251
1 files changed, 251 insertions, 0 deletions
diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c
new file mode 100644
index 0000000000..e7a5972750
--- /dev/null
+++ b/drivers/usb/storage/transport.c
@@ -0,0 +1,251 @@
+/*
+ * Most of this source has been derived from the Linux and
+ * U-Boot USB Mass Storage driver implementations.
+ *
+ * Adapted for barebox:
+ * Copyright (c) 2011, AMK Drives & Controls Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ */
+
+#include <common.h>
+#include <clock.h>
+#include <scsi.h>
+#include <errno.h>
+
+#undef USB_STOR_DEBUG
+
+#include "usb.h"
+#include "transport.h"
+
+
+/* The timeout argument of usb_bulk_msg() is actually ignored
+ and the timeout is hardcoded in the host driver */
+#define USB_BULK_TO 5000
+
+static __u32 cbw_tag = 0;
+
+/* direction table -- this indicates the direction of the data
+ * transfer for each command code (bit-encoded) -- 1 indicates input
+ * note that this doesn't work for shared command codes
+ */
+static const unsigned char us_direction[256/8] = {
+ 0x28, 0x81, 0x14, 0x14, 0x20, 0x01, 0x90, 0x77,
+ 0x0C, 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x40, 0x09, 0x01, 0x80, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+#define US_DIRECTION(x) ((us_direction[x>>3] >> (x & 7)) & 1)
+
+
+/*
+ * Bulk only transport
+ */
+
+/* Clear a stall on an endpoint - special for bulk-only devices */
+int usb_stor_Bulk_clear_endpt_stall(struct us_data *us, unsigned int pipe)
+{
+ return usb_clear_halt(us->pusb_dev, pipe);
+}
+
+/* Determine what the maximum LUN supported is */
+int usb_stor_Bulk_max_lun(struct us_data *us)
+{
+ int len;
+ unsigned char iobuf[1];
+
+ /* issue the command */
+ iobuf[0] = 0;
+ len = usb_control_msg(us->pusb_dev,
+ usb_rcvctrlpipe(us->pusb_dev, 0),
+ US_BULK_GET_MAX_LUN,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, us->ifnum, iobuf, 1, USB_CNTL_TIMEOUT);
+
+ US_DEBUGP("GetMaxLUN command result is %d, data is %d\n",
+ len, (int)iobuf[0]);
+
+ /* if we have a successful request, return the result */
+ if (len > 0)
+ return (int)iobuf[0];
+
+ /*
+ * Some devices don't like GetMaxLUN. They may STALL the control
+ * pipe, they may return a zero-length result, they may do nothing at
+ * all and timeout, or they may fail in even more bizarrely creative
+ * ways. In these cases the best approach is to use the default
+ * value: only one LUN.
+ */
+ return 0;
+}
+
+int usb_stor_Bulk_transport(ccb *srb, struct us_data *us)
+{
+ struct bulk_cb_wrap cbw;
+ struct bulk_cs_wrap csw;
+ int actlen, data_actlen;
+ int result;
+ unsigned int residue;
+ unsigned int pipein = usb_rcvbulkpipe(us->pusb_dev, us->recv_bulk_ep);
+ unsigned int pipeout = usb_sndbulkpipe(us->pusb_dev, us->send_bulk_ep);
+ int dir_in = US_DIRECTION(srb->cmd[0]);
+
+ srb->trans_bytes = 0;
+
+ /* set up the command wrapper */
+ cbw.Signature = cpu_to_le32(US_BULK_CB_SIGN);
+ cbw.DataTransferLength = cpu_to_le32(srb->datalen);
+ cbw.Flags = (dir_in ? US_BULK_FLAG_IN : US_BULK_FLAG_OUT);
+ cbw.Tag = ++cbw_tag;
+ cbw.Lun = srb->lun;
+ cbw.Length = srb->cmdlen;
+
+ /* copy the command payload */
+ memcpy(cbw.CDB, srb->cmd, cbw.Length);
+
+ /* send it to out endpoint */
+ US_DEBUGP("Bulk Command S 0x%x T 0x%x L %d F %d Trg %d LUN %d CL %d\n",
+ le32_to_cpu(cbw.Signature), cbw.Tag,
+ le32_to_cpu(cbw.DataTransferLength), cbw.Flags,
+ (cbw.Lun >> 4), (cbw.Lun & 0x0F),
+ cbw.Length);
+ result = usb_bulk_msg(us->pusb_dev, pipeout, &cbw, US_BULK_CB_WRAP_LEN,
+ &actlen, USB_BULK_TO);
+ US_DEBUGP("Bulk command transfer result=%d\n", result);
+ if (result < 0) {
+ usb_stor_Bulk_reset(us);
+ return USB_STOR_TRANSPORT_FAILED;
+ }
+
+ /* DATA STAGE */
+ /* send/receive data payload, if there is any */
+
+ wait_ms(1);
+
+ data_actlen = 0;
+ if (srb->datalen) {
+ unsigned int pipe = dir_in ? pipein : pipeout;
+ result = usb_bulk_msg(us->pusb_dev, pipe, srb->pdata,
+ srb->datalen, &data_actlen, USB_BULK_TO);
+ US_DEBUGP("Bulk data transfer result 0x%x\n", result);
+ /* special handling of STALL in DATA phase */
+ if ((result < 0) && (us->pusb_dev->status & USB_ST_STALLED)) {
+ US_DEBUGP("DATA: stall\n");
+ /* clear the STALL on the endpoint */
+ result = usb_stor_Bulk_clear_endpt_stall(us, pipe);
+ }
+ if (result < 0) {
+ US_DEBUGP("Device status: %lx\n", us->pusb_dev->status);
+ usb_stor_Bulk_reset(us);
+ return USB_STOR_TRANSPORT_FAILED;
+ }
+ }
+
+ /* STATUS phase + error handling */
+ US_DEBUGP("Attempting to get CSW...\n");
+ result = usb_bulk_msg(us->pusb_dev, pipein, &csw, US_BULK_CS_WRAP_LEN,
+ &actlen, USB_BULK_TO);
+
+ /* did the endpoint stall? */
+ if ((result < 0) && (us->pusb_dev->status & USB_ST_STALLED)) {
+ US_DEBUGP("STATUS: stall\n");
+ /* clear the STALL on the endpoint */
+ result = usb_stor_Bulk_clear_endpt_stall(us, pipein);
+ if (result >= 0) {
+ US_DEBUGP("Attempting to get CSW...\n");
+ result = usb_bulk_msg(us->pusb_dev, pipein,
+ &csw, US_BULK_CS_WRAP_LEN,
+ &actlen, USB_BULK_TO);
+ }
+ }
+
+ if (result < 0) {
+ US_DEBUGP("Device status: %lx\n", us->pusb_dev->status);
+ usb_stor_Bulk_reset(us);
+ return USB_STOR_TRANSPORT_FAILED;
+ }
+
+ /* check bulk status */
+ residue = le32_to_cpu(csw.Residue);
+ US_DEBUGP("Bulk Status S 0x%x T 0x%x R %u Stat 0x%x\n",
+ le32_to_cpu(csw.Signature), csw.Tag, residue, csw.Status);
+ if (csw.Signature != cpu_to_le32(US_BULK_CS_SIGN)) {
+ US_DEBUGP("Bad CSW signature\n");
+ usb_stor_Bulk_reset(us);
+ return USB_STOR_TRANSPORT_FAILED;
+ } else if (csw.Tag != cbw_tag) {
+ US_DEBUGP("Mismatching tag\n");
+ usb_stor_Bulk_reset(us);
+ return USB_STOR_TRANSPORT_FAILED;
+ } else if (csw.Status >= US_BULK_STAT_PHASE) {
+ US_DEBUGP("Status >= phase\n");
+ usb_stor_Bulk_reset(us);
+ return USB_STOR_TRANSPORT_ERROR;
+ } else if (residue > srb->datalen) {
+ US_DEBUGP("residue (%uB) > req data (%luB)\n",
+ residue, srb->datalen);
+ return USB_STOR_TRANSPORT_FAILED;
+ } else if (csw.Status == US_BULK_STAT_FAIL) {
+ US_DEBUGP("FAILED\n");
+ return USB_STOR_TRANSPORT_FAILED;
+ }
+ srb->trans_bytes = min(srb->datalen - residue, (ulong)data_actlen);
+
+ return 0;
+}
+
+
+/* This issues a Bulk-only Reset to the device in question, including
+ * clearing the subsequent endpoint halts that may occur.
+ */
+int usb_stor_Bulk_reset(struct us_data *us)
+{
+ int result;
+ int result2;
+ unsigned int pipe;
+
+ US_DEBUGP("%s called\n", __func__);
+
+ /* issue the command */
+ result = usb_control_msg(us->pusb_dev,
+ usb_sndctrlpipe(us->pusb_dev, 0),
+ US_BULK_RESET_REQUEST,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, us->ifnum, 0, 0, USB_CNTL_TIMEOUT);
+ if ((result < 0) && (us->pusb_dev->status & USB_ST_STALLED)) {
+ US_DEBUGP("Soft reset stalled: %d\n", result);
+ return result;
+ }
+ wait_ms(150);
+
+ /* clear the bulk endpoints halt */
+ US_DEBUGP("Soft reset: clearing %s endpoint halt\n", "bulk-in");
+ pipe = usb_rcvbulkpipe(us->pusb_dev, us->recv_bulk_ep);
+ result = usb_clear_halt(us->pusb_dev, pipe);
+ wait_ms(150);
+ US_DEBUGP("Soft reset: clearing %s endpoint halt\n", "bulk-out");
+ pipe = usb_sndbulkpipe(us->pusb_dev, us->send_bulk_ep);
+ result2 = usb_clear_halt(us->pusb_dev, pipe);
+ wait_ms(150);
+
+ if (result >= 0)
+ result = result2;
+ US_DEBUGP("Soft reset %s\n", ((result < 0) ? "failed" : "done"));
+
+ return result;
+}
+