Newer
Older
ret = EP_SUBMIT(priv->epbulkin, req);
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
if (ret != OK)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDREADSUBMIT), (uint16)-ret);
lun->sd = SCSI_KCQME_UNRRE1;
lun->sdinfo = priv->sector;
break;
}
/* Assume success... residue should probably really be decremented in
* wrcomplete when we know that the transfer completed successfully.
*/
priv->residue -= priv->nreqbytes;
priv->nreqbytes = 0;
}
}
usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDREADCMDFINISH), priv->u.xfrlen);
priv->thstate = USBSTRG_STATE_CMDFINISH;
return OK;
}
/****************************************************************************
* Name: usbstrg_cmdwritestate
*
* Description:
* Called from the worker thread in the USBSTRG_STATE_CMDWRITE state.
* The USBSTRG_STATE_CMDWRITE state is entered when usbstrg_cmdparsestate
* obtains a valid SCSI write command. This state is really a continuation
* of the USBSTRG_STATE_CMDPARSE state that handles extended SCSI write
* command handling.
*
* Returned value:
* If no USBDEV write request is available or certain other errors occur, this
* function returns a negated errno and stays in the USBSTRG_STATE_CMDWRITE
* state. Otherwise, when the new SCSI write command is fully processed,
* this function sets priv->thstate to USBSTRG_STATE_CMDFINISH and returns OK.
*
* State variables:
* xfrlen - holds the number of sectors read to be written.
* sector - holds the sector number of the next sector to write
* nsectbytes - holds the number of bytes buffered for the current sector
* nreqbytes - holds the number of untransferred bytes currently in the
* request at the head of the rdreqlist.
*
****************************************************************************/
static int usbstrg_cmdwritestate(FAR struct usbstrg_dev_s *priv)
{
FAR struct usbstrg_lun_s *lun = priv->lun;
FAR struct usbstrg_req_s *privreq;
FAR struct usbdev_req_s *req;
ssize_t nwritten;
uint16 xfrd;
ubyte *src;
ubyte *dest;
int nbytes;
int ret;
/* Loop transferring data until either (1) all of the data has been
* transferred, or (2) we have written all of the data in the available
* read requests.
*/
while (priv->u.xfrlen > 0)
{
usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDWRITE), priv->u.xfrlen);
/* Check if there is a request in the rdreqlist containing additional
* data to be written.
*/
privreq = (FAR struct usbstrg_req_s *)sq_peek(&priv->rdreqlist);
/* If there no request data available, then just return an error.
* This will cause us to remain in the CMDWRITE state. When a filled request is
* received, the worker thread will be awakened in the USBSTRG_STATE_CMDWRITE
* and we will be called again.
*/
if (!privreq)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDWRITERDRQEMPTY), 0);
priv->nreqbytes = 0;
return -ENOMEM;
}
req = privreq->req;
xfrd = req->xfrd;
priv->nreqbytes = xfrd;
/* Now loop until all of the data in the read request has been tranferred
* to the block driver OR all of the request data has been transferred.
*/
while (priv->nreqbytes > 0 && priv->u.xfrlen > 0)
{
/* Copy the data received in the read request into the sector I/O buffer */
src = &req->buf[xfrd - priv->nreqbytes];
dest = &priv->iobuffer[priv->nsectbytes];
nbytes = min(lun->sectorsize - priv->nsectbytes, priv->nreqbytes);
/* Copy the data from the sector buffer to the USB request and update counts */
memcpy(dest, src, nbytes);
priv->nsectbytes += nbytes;
priv->nreqbytes -= nbytes;
/* Is the I/O buffer full? */
if (priv->nsectbytes >= lun->sectorsize)
{
/* Yes.. Write the next sector */
nwritten = USBSTRG_DRVR_WRITE(lun, priv->iobuffer, priv->sector, 1);
if (nwritten < 0)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDWRITEWRITEFAIL), -nwritten);
lun->sd = SCSI_KCQME_WRITEFAULTAUTOREALLOCFAILED;
lun->sdinfo = priv->sector;
goto errout;
}
priv->nsectbytes = 0;
priv->residue -= lun->sectorsize;
priv->u.xfrlen--;
priv->sector++;
}
}
/* In either case, we are finished with this read request and can return it
* to the endpoint. Then we will go back to the top of the top and attempt
* to get the next read request.
*/
req->len = priv->epbulkout->maxpacket;
req->private = privreq;
req->callback = usbstrg_rdcomplete;
ret = EP_SUBMIT(priv->epbulkout, req);
if (ret != OK)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDWRITERDSUBMIT), (uint16)-ret);
}
/* Did the host decide to stop early? */
if (xfrd != priv->epbulkout->maxpacket)
{
priv->shortpacket = 1;
goto errout;
}
}
errout:
usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDWRITECMDFINISH), priv->u.xfrlen);
priv->thstate = USBSTRG_STATE_CMDFINISH;
return OK;
}
/****************************************************************************
* Name: usbstrg_cmdfinishstate
*
* Description:
* Called from the worker thread in the USBSTRG_STATE_CMDFINISH state.
* The USBSTRG_STATE_CMDFINISH state is entered when processing of a
* command has finished but before status has been returned.
*
* Returned value:
* If no USBDEV write request is available or certain other errors occur, this
* function returns a negated errno and stays in the USBSTRG_STATE_CMDFINISH
* state. Otherwise, when the command is fully processed, this function
* sets priv->thstate to USBSTRG_STATE_CMDSTATUS and returns OK.
*
****************************************************************************/
static int usbstrg_cmdfinishstate(FAR struct usbstrg_dev_s *priv)
{
FAR struct usbstrg_req_s *privreq;
irqstate_t flags;
int ret = OK;
/* Check if there is a request in the wrreqlist that we will be able to
* use for data transfer.
*/
privreq = (FAR struct usbstrg_req_s *)sq_peek(&priv->wrreqlist);
/* If there no request structures available, then just return an error.
* This will cause us to remain in the CMDREAD state. When a request is
* returned, the worker thread will be awakened in the USBSTRG_STATE_CMDREAD
* and we will be called again.
*/
if (!privreq)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHRQEMPTY), 0);
return -ENOMEM;
}
/* Finish the final stages of the reply */
switch (priv->cbwdir)
{
/* Device-to-host: All but the last data buffer has been sent */
case USBSTRG_FLAGS_DIRDEVICE2HOST:
if (priv->cbwlen > 0)
{
struct usbdev_req_s *req;
/* Take a request from the wrreqlist (we've already checked
* that is it not NULL)
*/
flags = irqsave();
privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->wrreqlist);
irqrestore(flags);
/* Send the write request */
req = privreq->req;
req->len = priv->nreqbytes;
req->callback = usbstrg_wrcomplete;
req->private = privreq;
req->flags = USBDEV_REQFLAGS_NULLPKT;
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
ret = EP_SUBMIT(priv->epbulkin, privreq->req);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHSUBMIT), (uint16)-ret);
}
/* Stall the BULK In endpoint if there is a residue */
if (priv->residue > 0)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHRESIDUE), (uint16)priv->residue);
(void)EP_STALL(priv->epbulkin);
}
}
break;
/* Host-to-device: We have processed all we want of the host data. */
case USBSTRG_FLAGS_DIRHOST2DEVICE:
if (priv->residue > 0)
{
/* Did the host stop sending unexpectedly early? */
flags = irqsave();
if (priv->shortpacket)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHSHORTPKT), (uint16)priv->residue);
}
/* Unprocessed incoming data: STALL and cancel requests. */
else
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINSHSUBMIT), (uint16)priv->residue);
EP_STALL(priv->epbulkout);
}
priv->theventset |= USBSTRG_EVENT_ABORTBULKOUT;
irqrestore(flags);
}
break;
default:
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINSHDIR), priv->cbwdir);
case USBSTRG_FLAGS_DIRNONE: /* Nothing to send */
break;
}
/* Return to the IDLE state */
usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDFINISHCMDSTATUS), 0);
priv->thstate = USBSTRG_STATE_CMDSTATUS;
return OK;
}
/****************************************************************************
* Name: usbstrg_cmdstatusstate
*
* Description:
* Called from the worker thread in the USBSTRG_STATE_CMDSTATUS state.
* That state is after a CBW has been fully processed. This function sends
* the concluding CSW.
*
* Returned value:
* If no write request is available or certain other errors occur, this
* function returns a negated errno and stays in the USBSTRG_STATE_CMDSTATUS
* state. Otherwise, when the SCSI statis is successfully returned, this
* function sets priv->thstate to USBSTRG_STATE_IDLE and returns OK.
*
****************************************************************************/
static int usbstrg_cmdstatusstate(FAR struct usbstrg_dev_s *priv)
{
FAR struct usbstrg_lun_s *lun = priv->lun;
FAR struct usbstrg_req_s *privreq;
FAR struct usbdev_req_s *req;
FAR struct usbstrg_csw_s *csw;
irqstate_t flags;
uint32 sd;
ubyte status = USBSTRG_CSWSTATUS_PASS;
int ret;
/* Take a request from the wrreqlist */
flags = irqsave();
privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->wrreqlist);
irqrestore(flags);
/* If there no request structures available, then just return an error.
* This will cause us to remain in the CMDSTATUS status. When a request is
* returned, the worker thread will be awakened in the USBSTRG_STATE_CMDSTATUS
* and we will be called again.
*/
if (!privreq)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDSTATUSRDREQLISTEMPTY), 0);
return -ENOMEM;
}
req = privreq->req;
csw = (struct usbstrg_csw_s*)req->buf;
/* Extract the sense data from the LUN structure */
if (lun)
{
sd = lun->sd;
}
else
{
sd = SCSI_KCQIR_INVALIDLUN;
}
/* Determine the CSW status: PASS, PHASEERROR, or FAIL */
if (priv->phaseerror)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SNDPHERROR), 0);
status = USBSTRG_CSWSTATUS_PHASEERROR;
sd = SCSI_KCQIR_INVALIDCOMMAND;
}
else if (sd != SCSI_KCQ_NOSENSE)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SNDCSWFAIL), 0);
status = USBSTRG_CSWSTATUS_FAIL;
}
/* Format and submit the CSW */
csw->signature[0] = 'U';
csw->signature[1] = 'S';
csw->signature[2] = 'B';
csw->signature[3] = 'S';
usbstrg_putle32(csw->tag, priv->cbwtag);
usbstrg_putle32(csw->residue, priv->residue);
csw->status = status;
usbstrg_dumpdata("SCSCI CSW", (ubyte*)csw, USBSTRG_CSW_SIZEOF);
req->len = USBSTRG_CSW_SIZEOF;
req->callback = usbstrg_wrcomplete;
req->private = privreq;
req->flags = USBDEV_REQFLAGS_NULLPKT;
ret = EP_SUBMIT(priv->epbulkin, req);
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SNDSTATUSSUBMIT), (uint16)-ret);
flags = irqsave();
(void)sq_addlast((sq_entry_t*)privreq, &priv->wrreqlist);
irqrestore(flags);
}
/* Return to the IDLE state */
usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDSTATUSIDLE), 0);
priv->thstate = USBSTRG_STATE_IDLE;
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: usbstrg_workerthread
*
* Description:
* This is the main function of the USB storage worker thread. It loops
* until USB-related events occur, then processes those events accordingly
*
****************************************************************************/
void *usbstrg_workerthread(void *arg)
{
struct usbstrg_dev_s *priv = (struct usbstrg_dev_s *)arg;
irqstate_t flags;
uint16 eventset;
int ret;
/* This thread is started before the USB storage class is fully initialized.
* wait here until we are told to begin. Start in the NOTINITIALIZED state
*/
pthread_mutex_lock(&priv->mutex);
priv->thstate = USBSTRG_STATE_STARTED;
while ((priv->theventset & USBSTRG_EVENT_READY) != 0 &&
(priv->theventset & USBSTRG_EVENT_TERMINATEREQUEST) != 0)
{
pthread_cond_wait(&priv->cond, &priv->mutex);
}
/* Transition to the INITIALIZED/IDLE state */
priv->thstate = USBSTRG_STATE_IDLE;
eventset = priv->theventset;
priv->theventset = USBSTRG_EVENT_NOEVENTS;
pthread_mutex_unlock(&priv->mutex);
/* Then loop until we are asked to terminate */
while (eventset != USBSTRG_EVENT_TERMINATEREQUEST)
{
/* Wait for some interesting event. Note that we must both take the
* lock (to eliminate race conditions with other threads) and disable
* interrupts (to eliminate race conditions with USB interrupt handling.
*/
pthread_mutex_lock(&priv->mutex);
flags = irqsave();
if (priv->theventset == USBSTRG_EVENT_NOEVENTS)
{
pthread_cond_wait(&priv->cond, &priv->mutex);
}
/* Sample any events before re-enabling interrupts. Any events that
* occur after re-enabling interrupts will have to be handled in the
* next time through the loop.
*/
eventset = priv->theventset;
priv->theventset = USBSTRG_EVENT_NOEVENTS;
irqrestore(flags);
/* Were we awakened by some event that requires immediate action? */
/* The USBSTRG_EVENT_DISCONNECT is signalled from the disconnect method
* after all transfers have been stopped, when the host is disconnected.
*/
if ((eventset & USBSTRG_EVENT_DISCONNECT) != 0)
{
#warning LOGIC needed
}
/* Called with the bulk-storage-specific USBSTRG_REQ_MSRESET EP0 setup
* received. We must stop the current operation and reinialize state.
*/
if ((eventset & USBSTRG_EVENT_RESET) != 0)
{
#warning LOGIC needed
priv->thstate = USBSTRG_STATE_IDLE;
usbstrg_deferredresponse(priv, FALSE);
}
/* The USBSTRG_EVENT_CFGCHANGE is signaled when the EP0 setup
* logic receives a USB_REQ_SETCONFIGURATION request
*/
if ((eventset & USBSTRG_EVENT_CFGCHANGE) != 0)
{
#warning LOGIC needed
usbstrg_setconfig(priv, priv->thvalue);
priv->thstate = USBSTRG_STATE_IDLE;
usbstrg_deferredresponse(priv, FALSE);
}
/* The USBSTRG_EVENT_IFCHANGE is signaled when the EP0 setup
* logic receives a USB_REQ_SETINTERFACE request
*/
if ((eventset & USBSTRG_EVENT_IFCHANGE) != 0)
{
#warning LOGIC needed
usbstrg_resetconfig(priv);
usbstrg_setconfig(priv, priv->thvalue);
priv->thstate = USBSTRG_STATE_IDLE;
usbstrg_deferredresponse(priv, FALSE);
}
/* Set by the CMDFINISH logic when there is a residue after processing
* a host-to-device transfer. We need to discard all incoming request.
*/
if ((eventset & USBSTRG_EVENT_ABORTBULKOUT) != 0)
{
#warning LOGIC needed
priv->thstate = USBSTRG_STATE_IDLE;
}
/* All other events are just wakeup calls and are intended only
* drive the state maching. Remember only the terminate request event...
* we'll process that at the end of the loop.
*/
eventset &= USBSTRG_EVENT_TERMINATEREQUEST;
/* Loop processing each SCSI command state. Each state handling
* function will do the following:
*
* - If it must block for an event, it will return a negated errno value
* - If it completes the processing for that state, it will (1) set
* the next appropriate state value and (2) return OK.
*
* So the following will loop until we must block for an event in
* a particular state. When we are awakened by an event (above) we
* will resume processing in the same state.
*/
for (ret = OK; ret == OK; )
{
switch (priv->thstate)
{
case USBSTRG_STATE_IDLE: /* Started and waiting for commands */
ret = usbstrg_idlestate(priv);
break;
case USBSTRG_STATE_CMDPARSE: /* Parsing the received a command */
ret = usbstrg_cmdparsestate(priv);
break;
case USBSTRG_STATE_CMDREAD: /* Continuing to process a SCSI read command */
ret = usbstrg_cmdreadstate(priv);
break;
case USBSTRG_STATE_CMDWRITE: /* Continuing to process a SCSI write command */
ret = usbstrg_cmdwritestate(priv);
break;
case USBSTRG_STATE_CMDFINISH: /* Finish command processing */
ret = usbstrg_cmdfinishstate(priv);
break;
case USBSTRG_STATE_CMDSTATUS: /* Processing the status phase of a command */
ret = usbstrg_cmdstatusstate(priv);
break;
case USBSTRG_STATE_NOTSTARTED: /* Thread has not yet been started */
case USBSTRG_STATE_STARTED: /* Started, but is not yet initialized */
case USBSTRG_STATE_TERMINATED: /* Thread has exitted */
default:
usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_INVALIDSTATE), priv->thstate);
priv->thstate = USBSTRG_STATE_IDLE;
break;
}
}
}
/* Transition to the TERMINATED state and exit */
priv->thstate = USBSTRG_STATE_TERMINATED;
return NULL;
}