summaryrefslogtreecommitdiffstats
path: root/drivers/usb/storage/transport.c
blob: cd66d2bfe699edd97686aea8cf668ace8908dd1d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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.
 *
 *
 */

#include <common.h>
#include <clock.h>
#include <scsi.h>
#include <errno.h>
#include <dma.h>

#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 */
static 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)
{
	struct device_d *dev = &us->pusb_dev->dev;
	int len, ret = 0;
	unsigned char *iobuf = dma_alloc(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);

	dev_dbg(dev, "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)
		ret = iobuf[0];

	dma_free(iobuf);

	/*
	 * 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 ret;
}

int usb_stor_Bulk_transport(ccb *srb, struct us_data *us)
{
	struct device_d *dev = &us->pusb_dev->dev;
	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 */
	dev_dbg(dev, "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);
	dev_dbg(dev, "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 */

	mdelay(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);
		dev_dbg(dev, "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)) {
			dev_dbg(dev, "DATA: stall\n");
			/* clear the STALL on the endpoint */
			result = usb_stor_Bulk_clear_endpt_stall(us, pipe);
		}
		if (result < 0) {
			dev_dbg(dev, "Device status: %lx\n", us->pusb_dev->status);
			usb_stor_Bulk_reset(us);
			return USB_STOR_TRANSPORT_FAILED;
		}
	}

	/* STATUS phase + error handling */
	dev_dbg(dev, "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)) {
		dev_dbg(dev, "STATUS: stall\n");
		/* clear the STALL on the endpoint */
		result = usb_stor_Bulk_clear_endpt_stall(us, pipein);
		if (result >= 0) {
			dev_dbg(dev, "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) {
		dev_dbg(dev, "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);
	dev_dbg(dev, "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)) {
		dev_dbg(dev, "Bad CSW signature\n");
		usb_stor_Bulk_reset(us);
		return USB_STOR_TRANSPORT_FAILED;
	} else if (csw.Tag != cbw_tag) {
		dev_dbg(dev, "Mismatching tag\n");
		usb_stor_Bulk_reset(us);
		return USB_STOR_TRANSPORT_FAILED;
	} else if (csw.Status >= US_BULK_STAT_PHASE) {
		dev_dbg(dev, "Status >= phase\n");
		usb_stor_Bulk_reset(us);
		return USB_STOR_TRANSPORT_ERROR;
	} else if (residue > srb->datalen) {
		dev_dbg(dev, "residue (%uB) > req data (%luB)\n",
		          residue, srb->datalen);
		return USB_STOR_TRANSPORT_FAILED;
	} else if (csw.Status == US_BULK_STAT_FAIL) {
		dev_dbg(dev, "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)
{
	struct device_d *dev = &us->pusb_dev->dev;
	int result;
	int result2;
	unsigned int pipe;

	dev_dbg(dev, "%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)) {
		dev_dbg(dev, "Soft reset stalled: %d\n", result);
		return result;
	}
	mdelay(150);

	/* clear the bulk endpoints halt */
	dev_dbg(dev, "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);
	mdelay(150);
	dev_dbg(dev, "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);
	mdelay(150);

	if (result >= 0)
		result = result2;
	dev_dbg(dev, "Soft reset %s\n", ((result < 0) ? "failed" : "done"));

	return result;
}