| /** @file | |
| Implementation of the USB mass storage Control/Bulk/Interrupt transport, | |
| according to USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport, Revision 1.1. | |
| Notice: it is being obsoleted by the standard body in favor of the BOT | |
| (Bulk-Only Transport). | |
| Copyright (c) 2007 - 2011, Intel Corporation. All rights reserved.<BR> | |
| This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include "UsbMass.h" | |
| // | |
| // Definition of USB CBI0 Transport Protocol | |
| // | |
| USB_MASS_TRANSPORT mUsbCbi0Transport = { | |
| USB_MASS_STORE_CBI0, | |
| UsbCbiInit, | |
| UsbCbiExecCommand, | |
| UsbCbiResetDevice, | |
| NULL, | |
| UsbCbiCleanUp | |
| }; | |
| // | |
| // Definition of USB CBI1 Transport Protocol | |
| // | |
| USB_MASS_TRANSPORT mUsbCbi1Transport = { | |
| USB_MASS_STORE_CBI1, | |
| UsbCbiInit, | |
| UsbCbiExecCommand, | |
| UsbCbiResetDevice, | |
| NULL, | |
| UsbCbiCleanUp | |
| }; | |
| /** | |
| Initializes USB CBI protocol. | |
| This function initializes the USB mass storage class CBI protocol. | |
| It will save its context which is a USB_CBI_PROTOCOL structure | |
| in the Context if Context isn't NULL. | |
| @param UsbIo The USB I/O Protocol instance | |
| @param Context The buffer to save the context to | |
| @retval EFI_SUCCESS The device is successfully initialized. | |
| @retval EFI_UNSUPPORTED The transport protocol doesn't support the device. | |
| @retval Other The USB CBI initialization fails. | |
| **/ | |
| EFI_STATUS | |
| UsbCbiInit ( | |
| IN EFI_USB_IO_PROTOCOL *UsbIo, | |
| OUT VOID **Context OPTIONAL | |
| ) | |
| { | |
| USB_CBI_PROTOCOL *UsbCbi; | |
| EFI_USB_INTERFACE_DESCRIPTOR *Interface; | |
| EFI_USB_ENDPOINT_DESCRIPTOR EndPoint; | |
| EFI_STATUS Status; | |
| UINT8 Index; | |
| // | |
| // Allocate the CBI context for USB_CBI_PROTOCOL and 3 endpoint descriptors. | |
| // | |
| UsbCbi = AllocateZeroPool ( | |
| sizeof (USB_CBI_PROTOCOL) + 3 * sizeof (EFI_USB_ENDPOINT_DESCRIPTOR) | |
| ); | |
| ASSERT (UsbCbi != NULL); | |
| UsbCbi->UsbIo = UsbIo; | |
| // | |
| // Get the interface descriptor and validate that it | |
| // is a USB Mass Storage CBI interface. | |
| // | |
| Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &UsbCbi->Interface); | |
| if (EFI_ERROR (Status)) { | |
| goto ON_ERROR; | |
| } | |
| Interface = &UsbCbi->Interface; | |
| if ((Interface->InterfaceProtocol != USB_MASS_STORE_CBI0) | |
| && (Interface->InterfaceProtocol != USB_MASS_STORE_CBI1)) { | |
| Status = EFI_UNSUPPORTED; | |
| goto ON_ERROR; | |
| } | |
| // | |
| // Locate and save the bulk-in, bulk-out, and interrupt endpoint | |
| // | |
| for (Index = 0; Index < Interface->NumEndpoints; Index++) { | |
| Status = UsbIo->UsbGetEndpointDescriptor (UsbIo, Index, &EndPoint); | |
| if (EFI_ERROR (Status)) { | |
| continue; | |
| } | |
| if (USB_IS_BULK_ENDPOINT (EndPoint.Attributes)) { | |
| // | |
| // Use the first Bulk-In and Bulk-Out endpoints | |
| // | |
| if (USB_IS_IN_ENDPOINT (EndPoint.EndpointAddress) && | |
| (UsbCbi->BulkInEndpoint == NULL)) { | |
| UsbCbi->BulkInEndpoint = (EFI_USB_ENDPOINT_DESCRIPTOR *) (UsbCbi + 1); | |
| CopyMem(UsbCbi->BulkInEndpoint, &EndPoint, sizeof (EndPoint));; | |
| } | |
| if (USB_IS_OUT_ENDPOINT (EndPoint.EndpointAddress) && | |
| (UsbCbi->BulkOutEndpoint == NULL)) { | |
| UsbCbi->BulkOutEndpoint = (EFI_USB_ENDPOINT_DESCRIPTOR *) (UsbCbi + 1) + 1; | |
| CopyMem(UsbCbi->BulkOutEndpoint, &EndPoint, sizeof (EndPoint)); | |
| } | |
| } else if (USB_IS_INTERRUPT_ENDPOINT (EndPoint.Attributes)) { | |
| // | |
| // Use the first interrupt endpoint if it is CBI0 | |
| // | |
| if ((Interface->InterfaceProtocol == USB_MASS_STORE_CBI0) && | |
| (UsbCbi->InterruptEndpoint == NULL)) { | |
| UsbCbi->InterruptEndpoint = (EFI_USB_ENDPOINT_DESCRIPTOR *) (UsbCbi + 1) + 2; | |
| CopyMem(UsbCbi->InterruptEndpoint, &EndPoint, sizeof (EndPoint)); | |
| } | |
| } | |
| } | |
| if ((UsbCbi->BulkInEndpoint == NULL) || (UsbCbi->BulkOutEndpoint == NULL)) { | |
| Status = EFI_UNSUPPORTED; | |
| goto ON_ERROR; | |
| } | |
| if ((Interface->InterfaceProtocol == USB_MASS_STORE_CBI0) && (UsbCbi->InterruptEndpoint == NULL)) { | |
| Status = EFI_UNSUPPORTED; | |
| goto ON_ERROR; | |
| } | |
| if (Context != NULL) { | |
| *Context = UsbCbi; | |
| } else { | |
| FreePool (UsbCbi); | |
| } | |
| return EFI_SUCCESS; | |
| ON_ERROR: | |
| FreePool (UsbCbi); | |
| return Status; | |
| } | |
| /** | |
| Send the command to the device using class specific control transfer. | |
| This function sends command to the device using class specific control transfer. | |
| The CBI contains three phases: Command, Data, and Status. This is Command phase. | |
| @param UsbCbi The USB CBI protocol | |
| @param Cmd The high level command to transfer to device | |
| @param CmdLen The length of the command | |
| @param Timeout The time to wait the command to finish | |
| @retval EFI_SUCCESS The command is sent to the device. | |
| @retval Others The command failed to transfer to device | |
| **/ | |
| EFI_STATUS | |
| UsbCbiSendCommand ( | |
| IN USB_CBI_PROTOCOL *UsbCbi, | |
| IN UINT8 *Cmd, | |
| IN UINT8 CmdLen, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| EFI_USB_DEVICE_REQUEST Request; | |
| EFI_STATUS Status; | |
| UINT32 TransStatus; | |
| UINTN DataLen; | |
| INTN Retry; | |
| // | |
| // Fill in the device request, CBI use the "Accept Device-Specific | |
| // Cmd" (ADSC) class specific request to send commands. | |
| // | |
| Request.RequestType = 0x21; | |
| Request.Request = 0; | |
| Request.Value = 0; | |
| Request.Index = UsbCbi->Interface.InterfaceNumber; | |
| Request.Length = CmdLen; | |
| Status = EFI_SUCCESS; | |
| Timeout = Timeout / USB_MASS_1_MILLISECOND; | |
| for (Retry = 0; Retry < USB_CBI_MAX_RETRY; Retry++) { | |
| // | |
| // Use USB I/O Protocol to send the command to the device | |
| // | |
| TransStatus = 0; | |
| DataLen = CmdLen; | |
| Status = UsbCbi->UsbIo->UsbControlTransfer ( | |
| UsbCbi->UsbIo, | |
| &Request, | |
| EfiUsbDataOut, | |
| Timeout, | |
| Cmd, | |
| DataLen, | |
| &TransStatus | |
| ); | |
| // | |
| // The device can fail the command by STALL the control endpoint. | |
| // It can delay the command by NAK the data or status stage, this | |
| // is a "class-specific exemption to the USB specification". Retry | |
| // if the command is NAKed. | |
| // | |
| if (EFI_ERROR (Status) && (TransStatus == EFI_USB_ERR_NAK)) { | |
| continue; | |
| } | |
| break; | |
| } | |
| return Status; | |
| } | |
| /** | |
| Transfer data between the device and host. | |
| This function transfers data between the device and host. | |
| The CBI contains three phases: Command, Data, and Status. This is Data phase. | |
| @param UsbCbi The USB CBI device | |
| @param DataDir The direction of the data transfer | |
| @param Data The buffer to hold the data for input or output. | |
| @param TransLen On input, the expected transfer length. | |
| On output, the length of data actually transferred. | |
| @param Timeout The time to wait for the command to execute | |
| @retval EFI_SUCCESS The data transferred successfully. | |
| @retval EFI_SUCCESS No data to transfer | |
| @retval Others Failed to transfer all the data | |
| **/ | |
| EFI_STATUS | |
| UsbCbiDataTransfer ( | |
| IN USB_CBI_PROTOCOL *UsbCbi, | |
| IN EFI_USB_DATA_DIRECTION DataDir, | |
| IN OUT UINT8 *Data, | |
| IN OUT UINTN *TransLen, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| EFI_USB_ENDPOINT_DESCRIPTOR *Endpoint; | |
| EFI_STATUS Status; | |
| UINT32 TransStatus; | |
| UINTN Remain; | |
| UINTN Increment; | |
| UINT8 *Next; | |
| UINTN Retry; | |
| // | |
| // If no data to transfer, just return EFI_SUCCESS. | |
| // | |
| if ((DataDir == EfiUsbNoData) || (*TransLen == 0)) { | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // Select the endpoint then issue the transfer | |
| // | |
| if (DataDir == EfiUsbDataIn) { | |
| Endpoint = UsbCbi->BulkInEndpoint; | |
| } else { | |
| Endpoint = UsbCbi->BulkOutEndpoint; | |
| } | |
| Next = Data; | |
| Remain = *TransLen; | |
| Retry = 0; | |
| Status = EFI_SUCCESS; | |
| Timeout = Timeout / USB_MASS_1_MILLISECOND; | |
| // | |
| // Transfer the data with a loop. The length of data transferred once is restricted. | |
| // | |
| while (Remain > 0) { | |
| TransStatus = 0; | |
| if (Remain > (UINTN) USB_CBI_MAX_PACKET_NUM * Endpoint->MaxPacketSize) { | |
| Increment = USB_CBI_MAX_PACKET_NUM * Endpoint->MaxPacketSize; | |
| } else { | |
| Increment = Remain; | |
| } | |
| Status = UsbCbi->UsbIo->UsbBulkTransfer ( | |
| UsbCbi->UsbIo, | |
| Endpoint->EndpointAddress, | |
| Next, | |
| &Increment, | |
| Timeout, | |
| &TransStatus | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| if (TransStatus == EFI_USB_ERR_NAK) { | |
| // | |
| // The device can NAK the host if either the data/buffer isn't | |
| // aviable or the command is in-progress. | |
| // If data are partially transferred, we just ignore NAK and continue. | |
| // If all data have been transferred and status is NAK, then we retry for several times. | |
| // If retry exceeds the USB_CBI_MAX_RETRY, then return error status. | |
| // | |
| if (Increment == 0) { | |
| if (++Retry > USB_CBI_MAX_RETRY) { | |
| goto ON_EXIT; | |
| } | |
| } else { | |
| Next += Increment; | |
| Remain -= Increment; | |
| Retry = 0; | |
| } | |
| continue; | |
| } | |
| // | |
| // The device can fail the command by STALL the bulk endpoint. | |
| // Clear the stall if that is the case. | |
| // | |
| if (TransStatus == EFI_USB_ERR_STALL) { | |
| UsbClearEndpointStall (UsbCbi->UsbIo, Endpoint->EndpointAddress); | |
| } | |
| goto ON_EXIT; | |
| } | |
| Next += Increment; | |
| Remain -= Increment; | |
| } | |
| ON_EXIT: | |
| *TransLen -= Remain; | |
| return Status; | |
| } | |
| /** | |
| Gets the result of high level command execution from interrupt endpoint. | |
| This function returns the USB transfer status, and put the high level | |
| command execution result in Result. | |
| The CBI contains three phases: Command, Data, and Status. This is Status phase. | |
| @param UsbCbi The USB CBI protocol | |
| @param Timeout The time to wait for the command to execute | |
| @param Result The result of the command execution. | |
| @retval EFI_SUCCESS The high level command execution result is | |
| retrieved in Result. | |
| @retval Others Failed to retrieve the result. | |
| **/ | |
| EFI_STATUS | |
| UsbCbiGetStatus ( | |
| IN USB_CBI_PROTOCOL *UsbCbi, | |
| IN UINT32 Timeout, | |
| OUT USB_CBI_STATUS *Result | |
| ) | |
| { | |
| UINTN Len; | |
| UINT8 Endpoint; | |
| EFI_STATUS Status; | |
| UINT32 TransStatus; | |
| INTN Retry; | |
| Endpoint = UsbCbi->InterruptEndpoint->EndpointAddress; | |
| Status = EFI_SUCCESS; | |
| Timeout = Timeout / USB_MASS_1_MILLISECOND; | |
| // | |
| // Attemp to the read the result from interrupt endpoint | |
| // | |
| for (Retry = 0; Retry < USB_CBI_MAX_RETRY; Retry++) { | |
| TransStatus = 0; | |
| Len = sizeof (USB_CBI_STATUS); | |
| Status = UsbCbi->UsbIo->UsbSyncInterruptTransfer ( | |
| UsbCbi->UsbIo, | |
| Endpoint, | |
| Result, | |
| &Len, | |
| Timeout, | |
| &TransStatus | |
| ); | |
| // | |
| // The CBI can NAK the interrupt endpoint if the command is in-progress. | |
| // | |
| if (EFI_ERROR (Status) && (TransStatus == EFI_USB_ERR_NAK)) { | |
| continue; | |
| } | |
| break; | |
| } | |
| return Status; | |
| } | |
| /** | |
| Execute USB mass storage command through the CBI0/CBI1 transport protocol. | |
| @param Context The USB CBI Protocol. | |
| @param Cmd The command to transfer to device | |
| @param CmdLen The length of the command | |
| @param DataDir The direction of data transfer | |
| @param Data The buffer to hold the data | |
| @param DataLen The length of the buffer | |
| @param Lun Should be 0, this field for bot only | |
| @param Timeout The time to wait | |
| @param CmdStatus The result of the command execution | |
| @retval EFI_SUCCESS The command is executed successfully. | |
| @retval Other Failed to execute the command | |
| **/ | |
| EFI_STATUS | |
| UsbCbiExecCommand ( | |
| 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 | |
| ) | |
| { | |
| USB_CBI_PROTOCOL *UsbCbi; | |
| USB_CBI_STATUS Result; | |
| EFI_STATUS Status; | |
| UINTN TransLen; | |
| *CmdStatus = USB_MASS_CMD_SUCCESS; | |
| UsbCbi = (USB_CBI_PROTOCOL *) Context; | |
| // | |
| // Send the command to the device. Return immediately if device | |
| // rejects the command. | |
| // | |
| Status = UsbCbiSendCommand (UsbCbi, Cmd, CmdLen, Timeout); | |
| if (EFI_ERROR (Status)) { | |
| gBS->Stall(10 * USB_MASS_1_MILLISECOND); | |
| DEBUG ((EFI_D_ERROR, "UsbCbiExecCommand: UsbCbiSendCommand (%r)\n",Status)); | |
| return Status; | |
| } | |
| // | |
| // Transfer the data. Return this status if no interrupt endpoint | |
| // is used to report the transfer status. | |
| // | |
| TransLen = (UINTN) DataLen; | |
| Status = UsbCbiDataTransfer (UsbCbi, DataDir, Data, &TransLen, Timeout); | |
| if (UsbCbi->InterruptEndpoint == NULL) { | |
| DEBUG ((EFI_D_ERROR, "UsbCbiExecCommand: UsbCbiDataTransfer (%r)\n",Status)); | |
| return Status; | |
| } | |
| // | |
| // Get the status. If it succeeds, interpret the result. | |
| // | |
| Status = UsbCbiGetStatus (UsbCbi, Timeout, &Result); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "UsbCbiExecCommand: UsbCbiGetStatus (%r)\n",Status)); | |
| return Status; | |
| } | |
| if (UsbCbi->Interface.InterfaceSubClass == USB_MASS_STORE_UFI) { | |
| // | |
| // For UFI device, ASC and ASCQ are returned. | |
| // | |
| // Do not set the USB_MASS_CMD_FAIL for a request sense command | |
| // as a bad result type doesn't mean a cmd failure | |
| // | |
| if (Result.Type != 0 && *(UINT8*)Cmd != 0x03) { | |
| *CmdStatus = USB_MASS_CMD_FAIL; | |
| } | |
| } else { | |
| // | |
| // Check page 27, CBI spec 1.1 for vaious reture status. | |
| // | |
| switch (Result.Value & 0x03) { | |
| case 0x00: | |
| // | |
| // Pass | |
| // | |
| *CmdStatus = USB_MASS_CMD_SUCCESS; | |
| break; | |
| case 0x02: | |
| // | |
| // Phase Error, response with reset. | |
| // No break here to fall through to "Fail". | |
| // | |
| UsbCbiResetDevice (UsbCbi, FALSE); | |
| case 0x01: | |
| // | |
| // Fail | |
| // | |
| *CmdStatus = USB_MASS_CMD_FAIL; | |
| break; | |
| case 0x03: | |
| // | |
| // Persistent Fail. Need to send REQUEST SENSE. | |
| // | |
| *CmdStatus = USB_MASS_CMD_PERSISTENT; | |
| break; | |
| } | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Reset the USB mass storage device by CBI protocol. | |
| This function resets the USB mass storage device by CBI protocol. | |
| The reset is defined as a non-data command. Don't use UsbCbiExecCommand | |
| to send the command to device because that may introduce recursive loop. | |
| @param Context The USB CBI protocol | |
| @param ExtendedVerification The flag controlling the rule of reset. | |
| Not used here. | |
| @retval EFI_SUCCESS The device is reset. | |
| @retval Others Failed to reset the device. | |
| **/ | |
| EFI_STATUS | |
| UsbCbiResetDevice ( | |
| IN VOID *Context, | |
| IN BOOLEAN ExtendedVerification | |
| ) | |
| { | |
| UINT8 ResetCmd[USB_CBI_RESET_CMD_LEN]; | |
| USB_CBI_PROTOCOL *UsbCbi; | |
| USB_CBI_STATUS Result; | |
| EFI_STATUS Status; | |
| UINT32 Timeout; | |
| UsbCbi = (USB_CBI_PROTOCOL *) Context; | |
| // | |
| // Fill in the reset command. | |
| // | |
| SetMem (ResetCmd, USB_CBI_RESET_CMD_LEN, 0xFF); | |
| ResetCmd[0] = 0x1D; | |
| ResetCmd[1] = 0x04; | |
| Timeout = USB_CBI_RESET_DEVICE_TIMEOUT / USB_MASS_1_MILLISECOND; | |
| // | |
| // Send the command to the device. Don't use UsbCbiExecCommand here. | |
| // | |
| Status = UsbCbiSendCommand (UsbCbi, ResetCmd, USB_CBI_RESET_CMD_LEN, Timeout); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_DEVICE_ERROR; | |
| } | |
| // | |
| // Just retrieve the status and ignore that. Then stall | |
| // 50ms to wait for it to complete. | |
| // | |
| UsbCbiGetStatus (UsbCbi, Timeout, &Result); | |
| gBS->Stall (USB_CBI_RESET_DEVICE_STALL); | |
| // | |
| // Clear the Bulk-In and Bulk-Out stall condition and init data toggle. | |
| // | |
| UsbClearEndpointStall (UsbCbi->UsbIo, UsbCbi->BulkInEndpoint->EndpointAddress); | |
| UsbClearEndpointStall (UsbCbi->UsbIo, UsbCbi->BulkOutEndpoint->EndpointAddress); | |
| return Status; | |
| } | |
| /** | |
| Clean up the CBI protocol's resource. | |
| @param Context The instance of CBI protocol. | |
| @retval EFI_SUCCESS The resource is cleaned up. | |
| **/ | |
| EFI_STATUS | |
| UsbCbiCleanUp ( | |
| IN VOID *Context | |
| ) | |
| { | |
| FreePool (Context); | |
| return EFI_SUCCESS; | |
| } |