mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-11-27 12:15:19 +01:00
6b33696c93
Signed-off-by: SergeySlice <sergey.slice@gmail.com>
607 lines
18 KiB
C
607 lines
18 KiB
C
/** @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 - 2018, Intel Corporation. All rights reserved.<BR>
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
**/
|
|
|
|
#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;
|
|
}
|