1 /** @file
2   Implementation of the USB mass storage Bulk-Only Transport protocol,
3   according to USB Mass Storage Class Bulk-Only Transport, Revision 1.0.
4 
5 Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
6 SPDX-License-Identifier: BSD-2-Clause-Patent
7 
8 **/
9 
10 #include "UsbMass.h"
11 
12 //
13 // Definition of USB BOT Transport Protocol
14 //
15 USB_MASS_TRANSPORT mUsbBotTransport = {
16   USB_MASS_STORE_BOT,
17   UsbBotInit,
18   UsbBotExecCommand,
19   UsbBotResetDevice,
20   UsbBotGetMaxLun,
21   UsbBotCleanUp
22 };
23 
24 /**
25   Initializes USB BOT protocol.
26 
27   This function initializes the USB mass storage class BOT protocol.
28   It will save its context which is a USB_BOT_PROTOCOL structure
29   in the Context if Context isn't NULL.
30 
31   @param  UsbIo                 The USB I/O Protocol instance
32   @param  Context               The buffer to save the context to
33 
34   @retval EFI_SUCCESS           The device is successfully initialized.
35   @retval EFI_UNSUPPORTED       The transport protocol doesn't support the device.
36   @retval Other                 The USB BOT initialization fails.
37 
38 **/
39 EFI_STATUS
UsbBotInit(IN EFI_USB_IO_PROTOCOL * UsbIo,OUT VOID ** Context OPTIONAL)40 UsbBotInit (
41   IN  EFI_USB_IO_PROTOCOL       *UsbIo,
42   OUT VOID                      **Context OPTIONAL
43   )
44 {
45   USB_BOT_PROTOCOL              *UsbBot;
46   EFI_USB_INTERFACE_DESCRIPTOR  *Interface;
47   EFI_USB_ENDPOINT_DESCRIPTOR   EndPoint;
48   EFI_STATUS                    Status;
49   UINT8                         Index;
50 
51   //
52   // Allocate the BOT context for USB_BOT_PROTOCOL and two endpoint descriptors.
53   //
54   UsbBot = AllocateZeroPool (sizeof (USB_BOT_PROTOCOL) + 2 * sizeof (EFI_USB_ENDPOINT_DESCRIPTOR));
55   ASSERT (UsbBot != NULL);
56 
57   UsbBot->UsbIo = UsbIo;
58 
59   //
60   // Get the interface descriptor and validate that it
61   // is a USB Mass Storage BOT interface.
62   //
63   Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &UsbBot->Interface);
64 
65   if (EFI_ERROR (Status)) {
66     goto ON_ERROR;
67   }
68 
69   Interface = &UsbBot->Interface;
70 
71   if (Interface->InterfaceProtocol != USB_MASS_STORE_BOT) {
72     Status = EFI_UNSUPPORTED;
73     goto ON_ERROR;
74   }
75 
76   //
77   // Locate and save the first bulk-in and bulk-out endpoint
78   //
79   for (Index = 0; Index < Interface->NumEndpoints; Index++) {
80     Status = UsbIo->UsbGetEndpointDescriptor (UsbIo, Index, &EndPoint);
81 
82     if (EFI_ERROR (Status) || !USB_IS_BULK_ENDPOINT (EndPoint.Attributes)) {
83       continue;
84     }
85 
86     if (USB_IS_IN_ENDPOINT (EndPoint.EndpointAddress) &&
87        (UsbBot->BulkInEndpoint == NULL)) {
88 
89       UsbBot->BulkInEndpoint  = (EFI_USB_ENDPOINT_DESCRIPTOR *) (UsbBot + 1);
90       CopyMem(UsbBot->BulkInEndpoint, &EndPoint, sizeof (EndPoint));
91     }
92 
93     if (USB_IS_OUT_ENDPOINT (EndPoint.EndpointAddress) &&
94        (UsbBot->BulkOutEndpoint == NULL)) {
95 
96       UsbBot->BulkOutEndpoint   = (EFI_USB_ENDPOINT_DESCRIPTOR *) (UsbBot + 1) + 1;
97       CopyMem (UsbBot->BulkOutEndpoint, &EndPoint, sizeof(EndPoint));
98     }
99   }
100 
101   //
102   // If bulk-in or bulk-out endpoint is not found, report error.
103   //
104   if ((UsbBot->BulkInEndpoint == NULL) || (UsbBot->BulkOutEndpoint == NULL)) {
105     Status = EFI_UNSUPPORTED;
106     goto ON_ERROR;
107   }
108 
109   //
110   // The USB BOT protocol uses CBWTag to match the CBW and CSW.
111   //
112   UsbBot->CbwTag = 0x01;
113 
114   if (Context != NULL) {
115     *Context = UsbBot;
116   } else {
117     FreePool (UsbBot);
118   }
119 
120   return EFI_SUCCESS;
121 
122 ON_ERROR:
123   FreePool (UsbBot);
124   return Status;
125 }
126 
127 /**
128   Send the command to the device using Bulk-Out endpoint.
129 
130   This function sends the command to the device using Bulk-Out endpoint.
131   BOT transfer is composed of three phases: Command, Data, and Status.
132   This is the Command phase.
133 
134   @param  UsbBot                The USB BOT device
135   @param  Cmd                   The command to transfer to device
136   @param  CmdLen                The length of the command
137   @param  DataDir               The direction of the data
138   @param  TransLen              The expected length of the data
139   @param  Lun                   The number of logic unit
140 
141   @retval EFI_SUCCESS           The command is sent to the device.
142   @retval EFI_NOT_READY         The device return NAK to the transfer
143   @retval Others                Failed to send the command to device
144 
145 **/
146 EFI_STATUS
UsbBotSendCommand(IN USB_BOT_PROTOCOL * UsbBot,IN UINT8 * Cmd,IN UINT8 CmdLen,IN EFI_USB_DATA_DIRECTION DataDir,IN UINT32 TransLen,IN UINT8 Lun)147 UsbBotSendCommand (
148   IN USB_BOT_PROTOCOL         *UsbBot,
149   IN UINT8                    *Cmd,
150   IN UINT8                    CmdLen,
151   IN EFI_USB_DATA_DIRECTION   DataDir,
152   IN UINT32                   TransLen,
153   IN UINT8                    Lun
154   )
155 {
156   USB_BOT_CBW               Cbw;
157   EFI_STATUS                Status;
158   UINT32                    Result;
159   UINTN                     DataLen;
160   UINTN                     Timeout;
161 
162   ASSERT ((CmdLen > 0) && (CmdLen <= USB_BOT_MAX_CMDLEN));
163 
164   //
165   // Fill in the Command Block Wrapper.
166   //
167   Cbw.Signature = USB_BOT_CBW_SIGNATURE;
168   Cbw.Tag       = UsbBot->CbwTag;
169   Cbw.DataLen   = TransLen;
170   Cbw.Flag      = (UINT8) ((DataDir == EfiUsbDataIn) ? BIT7 : 0);
171   Cbw.Lun       = Lun;
172   Cbw.CmdLen    = CmdLen;
173 
174   ZeroMem (Cbw.CmdBlock, USB_BOT_MAX_CMDLEN);
175   CopyMem (Cbw.CmdBlock, Cmd, CmdLen);
176 
177   Result  = 0;
178   DataLen = sizeof (USB_BOT_CBW);
179   Timeout = USB_BOT_SEND_CBW_TIMEOUT / USB_MASS_1_MILLISECOND;
180 
181   //
182   // Use USB I/O Protocol to send the Command Block Wrapper to the device.
183   //
184   Status = UsbBot->UsbIo->UsbBulkTransfer (
185                             UsbBot->UsbIo,
186                             UsbBot->BulkOutEndpoint->EndpointAddress,
187                             &Cbw,
188                             &DataLen,
189                             Timeout,
190                             &Result
191                             );
192   if (EFI_ERROR (Status)) {
193     if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL) && DataDir == EfiUsbDataOut) {
194       //
195       // Respond to Bulk-Out endpoint stall with a Reset Recovery,
196       // according to section 5.3.1 of USB Mass Storage Class Bulk-Only Transport Spec, v1.0.
197       //
198       UsbBotResetDevice (UsbBot, FALSE);
199     } else if (USB_IS_ERROR (Result, EFI_USB_ERR_NAK)) {
200       Status = EFI_NOT_READY;
201     }
202   }
203 
204   return Status;
205 }
206 
207 
208 /**
209   Transfer the data between the device and host.
210 
211   This function transfers the data between the device and host.
212   BOT transfer is composed of three phases: Command, Data, and Status.
213   This is the Data phase.
214 
215   @param  UsbBot                The USB BOT device
216   @param  DataDir               The direction of the data
217   @param  Data                  The buffer to hold data
218   @param  TransLen              The expected length of the data
219   @param  Timeout               The time to wait the command to complete
220 
221   @retval EFI_SUCCESS           The data is transferred
222   @retval EFI_SUCCESS           No data to transfer
223   @retval EFI_NOT_READY         The device return NAK to the transfer
224   @retval Others                Failed to transfer data
225 
226 **/
227 EFI_STATUS
UsbBotDataTransfer(IN USB_BOT_PROTOCOL * UsbBot,IN EFI_USB_DATA_DIRECTION DataDir,IN OUT UINT8 * Data,IN OUT UINTN * TransLen,IN UINT32 Timeout)228 UsbBotDataTransfer (
229   IN USB_BOT_PROTOCOL         *UsbBot,
230   IN EFI_USB_DATA_DIRECTION   DataDir,
231   IN OUT UINT8                *Data,
232   IN OUT UINTN                *TransLen,
233   IN UINT32                   Timeout
234   )
235 {
236   EFI_USB_ENDPOINT_DESCRIPTOR *Endpoint;
237   EFI_STATUS                  Status;
238   UINT32                      Result;
239 
240   //
241   // If no data to transfer, just return EFI_SUCCESS.
242   //
243   if ((DataDir == EfiUsbNoData) || (*TransLen == 0)) {
244     return EFI_SUCCESS;
245   }
246 
247   //
248   // Select the endpoint then issue the transfer
249   //
250   if (DataDir == EfiUsbDataIn) {
251     Endpoint = UsbBot->BulkInEndpoint;
252   } else {
253     Endpoint = UsbBot->BulkOutEndpoint;
254   }
255 
256   Result  = 0;
257   Timeout = Timeout / USB_MASS_1_MILLISECOND;
258 
259   Status = UsbBot->UsbIo->UsbBulkTransfer (
260                             UsbBot->UsbIo,
261                             Endpoint->EndpointAddress,
262                             Data,
263                             TransLen,
264                             Timeout,
265                             &Result
266                             );
267   if (EFI_ERROR (Status)) {
268     if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL)) {
269       DEBUG ((EFI_D_INFO, "UsbBotDataTransfer: (%r)\n", Status));
270       DEBUG ((EFI_D_INFO, "UsbBotDataTransfer: DataIn Stall\n"));
271       UsbClearEndpointStall (UsbBot->UsbIo, Endpoint->EndpointAddress);
272     } else if (USB_IS_ERROR (Result, EFI_USB_ERR_NAK)) {
273       Status = EFI_NOT_READY;
274     } else {
275       DEBUG ((EFI_D_ERROR, "UsbBotDataTransfer: (%r)\n", Status));
276     }
277     if(Status == EFI_TIMEOUT){
278       UsbBotResetDevice(UsbBot, FALSE);
279     }
280   }
281 
282   return Status;
283 }
284 
285 
286 /**
287   Get the command execution status from device.
288 
289   This function gets the command execution status from device.
290   BOT transfer is composed of three phases: Command, Data, and Status.
291   This is the Status phase.
292 
293   This function returns the transfer status of the BOT's CSW status,
294   and returns the high level command execution result in Result. So
295   even if EFI_SUCCESS is returned, the command may still have failed.
296 
297   @param  UsbBot         The USB BOT device.
298   @param  TransLen       The expected length of the data.
299   @param  CmdStatus      The result of the command execution.
300 
301   @retval EFI_SUCCESS    Command execute result is retrieved and in the Result.
302   @retval Other          Error occurred when trying to get status.
303 
304 **/
305 EFI_STATUS
UsbBotGetStatus(IN USB_BOT_PROTOCOL * UsbBot,IN UINT32 TransLen,OUT UINT8 * CmdStatus)306 UsbBotGetStatus (
307   IN  USB_BOT_PROTOCOL      *UsbBot,
308   IN  UINT32                TransLen,
309   OUT UINT8                 *CmdStatus
310   )
311 {
312   USB_BOT_CSW               Csw;
313   UINTN                     Len;
314   UINT8                     Endpoint;
315   EFI_STATUS                Status;
316   UINT32                    Result;
317   EFI_USB_IO_PROTOCOL       *UsbIo;
318   UINT32                    Index;
319   UINTN                     Timeout;
320 
321   *CmdStatus = USB_BOT_COMMAND_ERROR;
322   Status     = EFI_DEVICE_ERROR;
323   Endpoint   = UsbBot->BulkInEndpoint->EndpointAddress;
324   UsbIo      = UsbBot->UsbIo;
325   Timeout    = USB_BOT_RECV_CSW_TIMEOUT / USB_MASS_1_MILLISECOND;
326 
327   for (Index = 0; Index < USB_BOT_RECV_CSW_RETRY; Index++) {
328     //
329     // Attemp to the read Command Status Wrapper from bulk in endpoint
330     //
331     ZeroMem (&Csw, sizeof (USB_BOT_CSW));
332     Result = 0;
333     Len    = sizeof (USB_BOT_CSW);
334     Status = UsbIo->UsbBulkTransfer (
335                       UsbIo,
336                       Endpoint,
337                       &Csw,
338                       &Len,
339                       Timeout,
340                       &Result
341                       );
342     if (EFI_ERROR(Status)) {
343       if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL)) {
344         UsbClearEndpointStall (UsbIo, Endpoint);
345       }
346       continue;
347     }
348 
349     if (Csw.Signature != USB_BOT_CSW_SIGNATURE) {
350       //
351       // CSW is invalid, so perform reset recovery
352       //
353       Status = UsbBotResetDevice (UsbBot, FALSE);
354     } else if (Csw.CmdStatus == USB_BOT_COMMAND_ERROR) {
355       //
356       // Respond phase error also needs reset recovery
357       //
358       Status = UsbBotResetDevice (UsbBot, FALSE);
359     } else {
360       *CmdStatus = Csw.CmdStatus;
361       break;
362     }
363   }
364   //
365   //The tag is increased even if there is an error.
366   //
367   UsbBot->CbwTag++;
368 
369   return Status;
370 }
371 
372 
373 /**
374   Call the USB Mass Storage Class BOT protocol to issue
375   the command/data/status circle to execute the commands.
376 
377   @param  Context               The context of the BOT protocol, that is,
378                                 USB_BOT_PROTOCOL
379   @param  Cmd                   The high level command
380   @param  CmdLen                The command length
381   @param  DataDir               The direction of the data transfer
382   @param  Data                  The buffer to hold data
383   @param  DataLen               The length of the data
384   @param  Lun                   The number of logic unit
385   @param  Timeout               The time to wait command
386   @param  CmdStatus             The result of high level command execution
387 
388   @retval EFI_SUCCESS           The command is executed successfully.
389   @retval Other                 Failed to execute command
390 
391 **/
392 EFI_STATUS
UsbBotExecCommand(IN VOID * Context,IN VOID * Cmd,IN UINT8 CmdLen,IN EFI_USB_DATA_DIRECTION DataDir,IN VOID * Data,IN UINT32 DataLen,IN UINT8 Lun,IN UINT32 Timeout,OUT UINT32 * CmdStatus)393 UsbBotExecCommand (
394   IN  VOID                    *Context,
395   IN  VOID                    *Cmd,
396   IN  UINT8                   CmdLen,
397   IN  EFI_USB_DATA_DIRECTION  DataDir,
398   IN  VOID                    *Data,
399   IN  UINT32                  DataLen,
400   IN  UINT8                   Lun,
401   IN  UINT32                  Timeout,
402   OUT UINT32                  *CmdStatus
403   )
404 {
405   USB_BOT_PROTOCOL          *UsbBot;
406   EFI_STATUS                Status;
407   UINTN                     TransLen;
408   UINT8                     Result;
409 
410   *CmdStatus  = USB_MASS_CMD_FAIL;
411   UsbBot      = (USB_BOT_PROTOCOL *) Context;
412 
413   //
414   // Send the command to the device. Return immediately if device
415   // rejects the command.
416   //
417   Status = UsbBotSendCommand (UsbBot, Cmd, CmdLen, DataDir, DataLen, Lun);
418   if (EFI_ERROR (Status)) {
419     DEBUG ((EFI_D_ERROR, "UsbBotExecCommand: UsbBotSendCommand (%r)\n", Status));
420     return Status;
421   }
422 
423   //
424   // Transfer the data. Don't return immediately even data transfer
425   // failed. The host should attempt to receive the CSW no matter
426   // whether it succeeds or fails.
427   //
428   TransLen = (UINTN) DataLen;
429   UsbBotDataTransfer (UsbBot, DataDir, Data, &TransLen, Timeout);
430 
431   //
432   // Get the status, if that succeeds, interpret the result
433   //
434   Status = UsbBotGetStatus (UsbBot, DataLen, &Result);
435   if (EFI_ERROR (Status)) {
436     DEBUG ((EFI_D_ERROR, "UsbBotExecCommand: UsbBotGetStatus (%r)\n", Status));
437     return Status;
438   }
439 
440   if (Result == 0) {
441     *CmdStatus = USB_MASS_CMD_SUCCESS;
442   }
443 
444   return EFI_SUCCESS;
445 }
446 
447 
448 /**
449   Reset the USB mass storage device by BOT protocol.
450 
451   @param  Context               The context of the BOT protocol, that is,
452                                 USB_BOT_PROTOCOL.
453   @param  ExtendedVerification  If FALSE, just issue Bulk-Only Mass Storage Reset request.
454                                 If TRUE, additionally reset parent hub port.
455 
456   @retval EFI_SUCCESS           The device is reset.
457   @retval Others                Failed to reset the device..
458 
459 **/
460 EFI_STATUS
UsbBotResetDevice(IN VOID * Context,IN BOOLEAN ExtendedVerification)461 UsbBotResetDevice (
462   IN  VOID                    *Context,
463   IN  BOOLEAN                 ExtendedVerification
464   )
465 {
466   USB_BOT_PROTOCOL        *UsbBot;
467   EFI_USB_DEVICE_REQUEST  Request;
468   EFI_STATUS              Status;
469   UINT32                  Result;
470   UINT32                  Timeout;
471 
472   UsbBot = (USB_BOT_PROTOCOL *) Context;
473 
474   if (ExtendedVerification) {
475     //
476     // If we need to do strictly reset, reset its parent hub port
477     //
478     Status = UsbBot->UsbIo->UsbPortReset (UsbBot->UsbIo);
479     if (EFI_ERROR (Status)) {
480       return EFI_DEVICE_ERROR;
481     }
482   }
483 
484   //
485   // Issue a class specific Bulk-Only Mass Storage Reset request,
486   // according to section 3.1 of USB Mass Storage Class Bulk-Only Transport Spec, v1.0.
487   //
488   Request.RequestType = 0x21;
489   Request.Request     = USB_BOT_RESET_REQUEST;
490   Request.Value       = 0;
491   Request.Index       = UsbBot->Interface.InterfaceNumber;
492   Request.Length      = 0;
493   Timeout             = USB_BOT_RESET_DEVICE_TIMEOUT / USB_MASS_1_MILLISECOND;
494 
495   Status = UsbBot->UsbIo->UsbControlTransfer (
496                             UsbBot->UsbIo,
497                             &Request,
498                             EfiUsbNoData,
499                             Timeout,
500                             NULL,
501                             0,
502                             &Result
503                             );
504 
505   if (EFI_ERROR (Status)) {
506     return EFI_DEVICE_ERROR;
507   }
508 
509   //
510   // The device shall NAK the host's request until the reset is
511   // complete. We can use this to sync the device and host. For
512   // now just stall 100ms to wait for the device.
513   //
514   gBS->Stall (USB_BOT_RESET_DEVICE_STALL);
515 
516   //
517   // Clear the Bulk-In and Bulk-Out stall condition.
518   //
519   UsbClearEndpointStall (UsbBot->UsbIo, UsbBot->BulkInEndpoint->EndpointAddress);
520   UsbClearEndpointStall (UsbBot->UsbIo, UsbBot->BulkOutEndpoint->EndpointAddress);
521 
522   return Status;
523 }
524 
525 
526 /**
527   Get the max LUN (Logical Unit Number) of USB mass storage device.
528 
529   @param  Context          The context of the BOT protocol, that is, USB_BOT_PROTOCOL
530   @param  MaxLun           Return pointer to the max number of LUN. (e.g. MaxLun=1 means LUN0 and
531                            LUN1 in all.)
532 
533   @retval EFI_SUCCESS      Max LUN is got successfully.
534   @retval Others           Fail to execute this request.
535 
536 **/
537 EFI_STATUS
UsbBotGetMaxLun(IN VOID * Context,OUT UINT8 * MaxLun)538 UsbBotGetMaxLun (
539   IN  VOID                    *Context,
540   OUT UINT8                   *MaxLun
541   )
542 {
543   USB_BOT_PROTOCOL        *UsbBot;
544   EFI_USB_DEVICE_REQUEST  Request;
545   EFI_STATUS              Status;
546   UINT32                  Result;
547   UINT32                  Timeout;
548 
549   if (Context == NULL || MaxLun == NULL) {
550     return EFI_INVALID_PARAMETER;
551   }
552 
553   UsbBot = (USB_BOT_PROTOCOL *) Context;
554 
555   //
556   // Issue a class specific Bulk-Only Mass Storage get max lun reqest.
557   // according to section 3.2 of USB Mass Storage Class Bulk-Only Transport Spec, v1.0.
558   //
559   Request.RequestType = 0xA1;
560   Request.Request     = USB_BOT_GETLUN_REQUEST;
561   Request.Value       = 0;
562   Request.Index       = UsbBot->Interface.InterfaceNumber;
563   Request.Length      = 1;
564   Timeout             = USB_BOT_RESET_DEVICE_TIMEOUT / USB_MASS_1_MILLISECOND;
565 
566   Status = UsbBot->UsbIo->UsbControlTransfer (
567                             UsbBot->UsbIo,
568                             &Request,
569                             EfiUsbDataIn,
570                             Timeout,
571                             (VOID *) MaxLun,
572                             1,
573                             &Result
574                             );
575   if (EFI_ERROR (Status) || *MaxLun > USB_BOT_MAX_LUN) {
576     //
577     // If the Get LUN request returns an error or the MaxLun is larger than
578     // the maximum LUN value (0x0f) supported by the USB Mass Storage Class
579     // Bulk-Only Transport Spec, then set MaxLun to 0.
580     //
581     // This improves compatibility with USB FLASH drives that have a single LUN
582     // and either do not return a max LUN value or return an invalid maximum LUN
583     // value.
584     //
585     *MaxLun = 0;
586   }
587 
588   return EFI_SUCCESS;
589 }
590 
591 /**
592   Clean up the resource used by this BOT protocol.
593 
594   @param  Context         The context of the BOT protocol, that is, USB_BOT_PROTOCOL.
595 
596   @retval EFI_SUCCESS     The resource is cleaned up.
597 
598 **/
599 EFI_STATUS
UsbBotCleanUp(IN VOID * Context)600 UsbBotCleanUp (
601   IN  VOID                    *Context
602   )
603 {
604   FreePool (Context);
605   return EFI_SUCCESS;
606 }
607 
608