alder_lake_bios/Insyde/InsydeModulePkg/Bus/Usb/UsbBotDxe/UsbMassBot.c

1185 lines
35 KiB
C

/** @file
USB Mass Storage BOT Driver
;******************************************************************************
;* Copyright (c) 2012 - 2018, Insyde Software Corp. All Rights Reserved.
;*
;* You may not reproduce, distribute, publish, display, perform, modify, adapt,
;* transmit, broadcast, present, recite, release, license or otherwise exploit
;* any part of this publication in any form, by any means, without the prior
;* written permission of Insyde Software Corporation.
;*
;******************************************************************************
*/
#include "UsbMassBot.h"
#ifndef MDEPKG_NDEBUG
UINTN mUsbBotInfo = EFI_D_INFO;
UINTN mUsbBotError = EFI_D_ERROR;
#endif
//
// Function prototypes
//
EFI_STATUS
EFIAPI
UsbMassBotDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
STATIC EFI_GUID mUsbMassTransportProtocolGuid = USB_MASS_TRANSPORT_PROTOCOL_GUID;
STATIC EFI_USB_CORE_PROTOCOL *mUsbCore;
STATIC BOOLEAN mInSmram;
STATIC USB_MASS_TRANSPORT_PROTOCOL *mUsbBotTransport;
STATIC UINT16 mBypassCswTimeoutDevices[] = {
0x090c, 0x1000, 0xffff, // USB 1.1 "USB CYBER DISK" thumb drive with retry
0x08ec, 0x0015, 0xffff, // USB 2.0 "I-O DATA USB Flash Disk" thumb drive with retry
0x1f75, 0x0902, 0x0001, // USB 3.0 "SP Silcon Power 16GB" thumb drive without retry
0x054c, 0x05b9, 0x0001, // USB 3.0 "SONY USM-Q 32GB" thumb drive without retry
0x125F, 0x105B, 0x0001, // USB 3.0 "ADATA N005" thumb drive without retry
0x8564, 0x1000, 0x0001 // USB 3.0 "Transcend JetFlash 16G" thumb drive without retry
};
/**
Register protocol for this driver.
@param ImageHandle EFI_HANDLE
@param SystemTable EFI_SYSTEM_TABLE pointer
@retval EFI_SUCCESS Driver loaded
@retval other Driver not loaded
**/
EFI_STATUS
EFIAPI
UsbMassBotDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_HANDLE Handle;
UINTN Index;
UINTN HandleCount;
EFI_HANDLE *HandleBuffer;
USB_MASS_TRANSPORT_PROTOCOL *Transport;
//
// Locate UsbCore protocol
//
Status = gBS->LocateProtocol (
&gEfiUsbCoreProtocolGuid,
NULL,
(VOID **)&mUsbCore
);
if (EFI_ERROR (Status)) {
return EFI_UNSUPPORTED;
}
//
// Check the phase of instance
//
mUsbCore->IsInSmm (&mInSmram);
if (!mInSmram) {
//
// Now in boot service, install instance
//
Status = mUsbCore->AllocateBuffer (
sizeof (USB_MASS_TRANSPORT_PROTOCOL),
ALIGNMENT_32,
(VOID **)&mUsbBotTransport
);
if (EFI_ERROR (Status)) {
return Status;
}
mUsbBotTransport->Protocol = USB_MASS_STORE_BOT;
mUsbBotTransport->Init = UsbBotInit;
mUsbBotTransport->ExecCommand = UsbBotExecCommand;
mUsbBotTransport->Reset = UsbBotResetDevice;
mUsbBotTransport->Fini = UsbBotFini;
//
// Pass in a NULL to install to a new handle
//
Handle = NULL;
Status = gBS->InstallProtocolInterface (
&Handle,
&mUsbMassTransportProtocolGuid,
EFI_NATIVE_INTERFACE,
mUsbBotTransport
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Register module in DXE instance
//
mUsbCore->ModuleRegistration (ImageHandle);
} else {
//
// SMM instance, disable DEBUG message out
//
#ifndef MDEPKG_NDEBUG
mUsbBotInfo = 0;
mUsbBotError = 0;
#endif
//
// Now in SMM, get instance installed by boot service
//
Status = gBS->LocateHandleBuffer (
ByProtocol,
&mUsbMassTransportProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
if (EFI_ERROR(Status)) {
return Status;
}
Transport = NULL;
for (Index = 0; Index < HandleCount; Index ++) {
Status = gBS->HandleProtocol (
HandleBuffer[Index],
&mUsbMassTransportProtocolGuid,
(VOID **)&Transport
);
if (EFI_ERROR(Status)) {
continue;
}
//
// Setup appropriate variable in the Smm region
//
if (Transport->Protocol == USB_MASS_STORE_BOT) {
mUsbBotTransport = Transport;
break;
}
}
gBS->FreePool (HandleBuffer);
//
// Setup UsbCore for use by SMM instance
//
mUsbCore->AddressConvert (
SMM_ADDRESS,
(VOID*)(UINTN)mUsbCore,
(VOID**)&mUsbCore
);
//
// Register module in SMM instance
//
mUsbCore->ModuleRegistration (ImageHandle);
//
// Setup Smm address convert table for Smm security policy
//
mUsbCore->InsertAddressConvertTable (
ACT_FUNCTION_POINTER,
&mUsbBotTransport->Init,
4
);
mUsbCore->InsertAddressConvertTable (
ACT_INSTANCE_BODY,
mUsbBotTransport,
sizeof (USB_MASS_TRANSPORT_PROTOCOL)
);
mUsbCore->InsertAddressConvertTable (
ACT_INSTANCE_POINTER,
&mUsbBotTransport,
1
);
}
return Status;
}
/**
This function is used to manage a USB device with a control transfer pipe.
@param This Indicates calling context.
@param Request A pointer to the USB device request that will be sent to
the USB device.
@param Direction Indicates the data direction.
@param Data A pointer to the buffer of data that will be transmitted
to USB device or received from USB device.
@param Timeout Indicates the transfer should be completed within this time
frame.
@param DataLength The size, in bytes, of the data buffer specified by Data.
@param Status A pointer to the result of the USB transfer.
@retval EFI_SUCCESS
@retval EFI_INVALID_PARAMETER
@retval EFI_OUT_OF_RESOURCES
@retval EFI_TIMEOUT
@retval EFI_DEVICE_ERROR
**/
STATIC
EFI_STATUS
EFIAPI
UsbControlTransfer (
IN EFI_USB_IO_PROTOCOL *This,
IN EFI_USB_DEVICE_REQUEST *Request,
IN EFI_USB_DATA_DIRECTION Direction,
IN UINT32 Timeout,
IN OUT VOID *Data, OPTIONAL
IN UINTN DataLength, OPTIONAL
OUT UINT32 *Status
)
{
EFI_USB_IO_CONTROL_TRANSFER UsbControlTransferFunc = This->UsbControlTransfer;
if (mInSmram) {
mUsbCore->AddressConvert (
SMM_ADDRESS,
(VOID*)(UINTN)UsbControlTransferFunc,
(VOID**)&UsbControlTransferFunc
);
}
return UsbControlTransferFunc (
This,
Request,
Direction,
Timeout,
Data,
DataLength,
Status
);
}
/**
This function is used to manage a USB device with the bulk transfer pipe.
@param This Indicates calling context.
@param DeviceEndpoint The destination USB device endpoint to which the device
request is being sent.
@param Data A pointer to the buffer of data that will be transmitted
to USB device or received from USB device.
@param DataLength On input, the size, in bytes, of the data buffer
specified by Data. On output, the number of bytes that
were actually transferred.
@param Timeout Indicates the transfer should be completed within this
time frame.
@param Status This parameter indicates the USB transfer status.
@retval EFI_SUCCESS
@retval EFI_INVALID_PARAMETER
@retval EFI_OUT_OF_RESOURCES
@retval EFI_TIMEOUT
@retval EFI_DEVICE_ERROR
**/
EFI_STATUS
EFIAPI
UsbBulkTransfer (
IN EFI_USB_IO_PROTOCOL *This,
IN UINT8 DeviceEndpoint,
IN OUT VOID *Data,
IN OUT UINTN *DataLength,
IN UINTN Timeout,
OUT UINT32 *Status
)
{
EFI_USB_IO_BULK_TRANSFER UsbBulkTransferFunc = This->UsbBulkTransfer;
if (mInSmram) {
mUsbCore->AddressConvert (
SMM_ADDRESS,
(VOID*)(UINTN)UsbBulkTransferFunc,
(VOID**)&UsbBulkTransferFunc
);
}
return UsbBulkTransferFunc (
This,
DeviceEndpoint,
Data,
DataLength,
Timeout,
Status
);
}
/**
Retrieves the USB Device Descriptor.
@param This Indicates the calling context.
@param DeviceDescriptor A pointer to the caller allocated USB Device
Descriptor.
@retval EFI_SUCCESS
@retval EFI_INVALID_PARAMETER
@retval EFI_NOT_FOUND
**/
EFI_STATUS
EFIAPI
UsbGetDeviceDescriptor (
IN EFI_USB_IO_PROTOCOL *This,
OUT EFI_USB_DEVICE_DESCRIPTOR *DeviceDescriptor
)
{
EFI_USB_IO_GET_DEVICE_DESCRIPTOR UsbGetDeviceDescriptorFunc = This->UsbGetDeviceDescriptor;
if (mInSmram) {
mUsbCore->AddressConvert (
SMM_ADDRESS,
(VOID*)(UINTN)UsbGetDeviceDescriptorFunc,
(VOID**)&UsbGetDeviceDescriptorFunc
);
}
return UsbGetDeviceDescriptorFunc (
This,
DeviceDescriptor
);
}
/**
Retrieves the interface Descriptor for that controller.
@param This Indicates the calling context.
@param InterfaceDescriptor A pointer to the caller allocated USB interface
Descriptor.
@retval EFI_SUCCESS
@retval EFI_INVALID_PARAMETER
@retval EFI_NOT_FOUND
**/
STATIC
EFI_STATUS
EFIAPI
UsbGetInterfaceDescriptor (
IN EFI_USB_IO_PROTOCOL *This,
OUT EFI_USB_INTERFACE_DESCRIPTOR *InterfaceDescriptor
)
{
EFI_USB_IO_GET_INTERFACE_DESCRIPTOR UsbGetInterfaceDescriptorFunc = This->UsbGetInterfaceDescriptor;
if (mInSmram) {
mUsbCore->AddressConvert (
SMM_ADDRESS,
(VOID*)(UINTN)UsbGetInterfaceDescriptorFunc,
(VOID**)&UsbGetInterfaceDescriptorFunc
);
}
return UsbGetInterfaceDescriptorFunc (
This,
InterfaceDescriptor
);
}
/**
Retrieves the endpoint Descriptor for a given endpoint.
@param This Indicates the calling context.
@param EndpointIndex Indicates which endpoint descriptor to retrieve.
The valid range is 0..15.
@param EndpointDescriptor A pointer to the caller allocated USB Endpoint
Descriptor of a USB controller.
@retval EFI_SUCCESS The endpoint descriptor was retrieved successfully.
@retval EFI_INVALID_PARAMETER EndpointIndex is not valid.
EndpointDescriptor is NULL.
@retval EFI_NOT_FOUND The endpoint descriptor cannot be found.
The device may not be correctly configured.
**/
STATIC
EFI_STATUS
EFIAPI
UsbGetEndpointDescriptor (
IN EFI_USB_IO_PROTOCOL *This,
IN UINT8 EndpointIndex,
OUT EFI_USB_ENDPOINT_DESCRIPTOR *EndpointDescriptor
)
{
EFI_USB_IO_GET_ENDPOINT_DESCRIPTOR UsbGetEndpointDescriptorFunc = This->UsbGetEndpointDescriptor;
if (mInSmram) {
mUsbCore->AddressConvert (
SMM_ADDRESS,
(VOID*)(UINTN)UsbGetEndpointDescriptorFunc,
(VOID**)&UsbGetEndpointDescriptorFunc
);
}
return UsbGetEndpointDescriptorFunc (
This,
EndpointIndex,
EndpointDescriptor
);
}
/**
Resets and reconfigures the USB controller. This function will
work for all USB devices except USB Hub Controllers.
@param This Indicates the calling context.
@retval EFI_SUCCESS
@retval EFI_INVALID_PARAMETER
@retval EFI_DEVICE_ERROR
**/
EFI_STATUS
EFIAPI
UsbPortReset (
IN EFI_USB_IO_PROTOCOL *This
)
{
EFI_USB_IO_PORT_RESET UsbPortResetFunc = This->UsbPortReset;
if (mInSmram) {
mUsbCore->AddressConvert (
SMM_ADDRESS,
(VOID*)(UINTN)UsbPortResetFunc,
(VOID**)&UsbPortResetFunc
);
}
return UsbPortResetFunc (
This
);
}
/**
Initialize the USB mass storage class BOT transport protocol.
It will save its context which is a USB_BOT_PROTOCOL structure
in the Context if Context isn't NULL.
@param UsbIo The USB IO protocol to use
@param Controller The controller to init
@param Context The variable to save the context to
@retval EFI_SUCCESS The device is supported and protocol initialized.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory
@retval EFI_UNSUPPORTED The transport protocol doesn't support the device.
**/
STATIC
EFI_STATUS
UsbBotInit (
IN EFI_USB_IO_PROTOCOL * UsbIo,
IN EFI_HANDLE Controller,
OUT VOID **Context OPTIONAL
)
{
USB_BOT_PROTOCOL *UsbBot;
EFI_USB_INTERFACE_DESCRIPTOR *Interface;
EFI_USB_ENDPOINT_DESCRIPTOR EndPoint;
EFI_USB_DEVICE_DESCRIPTOR DeviceDescriptor;
EFI_STATUS Status;
UINT8 Index;
//
// Allocate the BOT context, append two endpoint descriptors to it
//
Status = mUsbCore->AllocateBuffer (
sizeof (USB_BOT_PROTOCOL) + 2 * sizeof (EFI_USB_ENDPOINT_DESCRIPTOR),
ALIGNMENT_32,
(VOID **)&UsbBot
);
if (EFI_ERROR (Status)) {
return EFI_OUT_OF_RESOURCES;
}
UsbBot->UsbIo = UsbIo;
//
// Get the interface descriptor and validate that it
// is a USB MSC BOT interface.
//
Status = UsbGetInterfaceDescriptor (UsbIo, &UsbBot->Interface);
if (EFI_ERROR (Status)) {
DEBUG ((mUsbBotError, "UsbBotInit: Get invalid BOT interface (%r)\n", Status));
goto ON_ERROR;
}
Interface = &UsbBot->Interface;
//
// Locate and save the first bulk-in and bulk-out endpoint
//
for (Index = 0; Index < Interface->NumEndpoints; Index++) {
Status = UsbGetEndpointDescriptor (UsbIo, Index, &EndPoint);
if (EFI_ERROR (Status) || !USB_IS_BULK_ENDPOINT (EndPoint.Attributes)) {
continue;
}
if (USB_IS_IN_ENDPOINT (EndPoint.EndpointAddress) &&
(UsbBot->BulkInEndpoint == NULL)) {
UsbBot->BulkInEndpoint = (EFI_USB_ENDPOINT_DESCRIPTOR *) (UsbBot + 1);
*UsbBot->BulkInEndpoint = EndPoint;
}
if (USB_IS_OUT_ENDPOINT (EndPoint.EndpointAddress) &&
(UsbBot->BulkOutEndpoint == NULL)) {
UsbBot->BulkOutEndpoint = (EFI_USB_ENDPOINT_DESCRIPTOR *) (UsbBot + 1) + 1;
*UsbBot->BulkOutEndpoint = EndPoint;
}
}
if ((UsbBot->BulkInEndpoint == NULL) || (UsbBot->BulkOutEndpoint == NULL)) {
DEBUG ((mUsbBotError, "UsbBotInit: In/Out Endpoint invalid\n"));
Status = EFI_UNSUPPORTED;
goto ON_ERROR;
}
//
// Inspect specific device to bypass CSW timeout
//
Status = UsbGetDeviceDescriptor (UsbIo, &DeviceDescriptor);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
for (Index = 0; Index < sizeof (mBypassCswTimeoutDevices) / sizeof (UINT16); Index += 3) {
if (DeviceDescriptor.IdVendor == mBypassCswTimeoutDevices[Index] &&
DeviceDescriptor.IdProduct == mBypassCswTimeoutDevices[Index + 1]) {
UsbBot->BypassCswTimeout = mBypassCswTimeoutDevices[Index + 2];
}
}
//
// The USB BOT protocol uses dCBWTag to match the CBW and CSW.
//
UsbBot->CbwTag = 0x01;
if (Context != NULL) {
//
// Setup the data for UsbLegacy
//
CopyMem(&UsbBot->Transport, mUsbBotTransport, sizeof(USB_MASS_TRANSPORT_PROTOCOL));
//
// Setup Smm address convert table for Smm security policy
//
mUsbCore->InsertAddressConvertTable (
ACT_FUNCTION_POINTER,
&UsbBot->Transport.Init,
4
);
mUsbCore->InsertAddressConvertTable (
ACT_INSTANCE_BODY,
UsbBot,
sizeof (USB_BOT_PROTOCOL) + 2 * sizeof (EFI_USB_ENDPOINT_DESCRIPTOR)
);
mUsbCore->InsertAddressConvertTable (
ACT_INSTANCE_POINTER,
&UsbBot->BulkInEndpoint,
1
);
mUsbCore->InsertAddressConvertTable (
ACT_INSTANCE_POINTER,
&UsbBot->BulkOutEndpoint,
1
);
mUsbCore->InsertAddressConvertTable (
ACT_INSTANCE_POINTER,
&UsbBot->UsbIo,
1
);
*Context = UsbBot;
} else {
//
// Free the instance due to the calling purpose is just to check is the device matches the protocol
//
Status = EFI_SUCCESS;
goto ON_ERROR;
}
return EFI_SUCCESS;
ON_ERROR:
mUsbCore->FreeBuffer (
sizeof (USB_BOT_PROTOCOL) + 2 * sizeof (EFI_USB_ENDPOINT_DESCRIPTOR),
UsbBot
);
return Status;
}
/**
Send the command to the device using Bulk-Out endpoint
@param UsbBot The USB BOT device
@param Cmd The command to transfer to device
@param CmdLen the length of the command
@param DataDir The direction of the data
@param TransLen The expected length of the data
@retval EFI_SUCCESS The command is sent to the device.
@retval EFI_NOT_READY The device return NAK to the transfer
@retval Others Failed to send the command to device
**/
STATIC
EFI_STATUS
UsbBotSendCommand (
IN USB_BOT_PROTOCOL *UsbBot,
IN UINT8 *Cmd,
IN UINT8 CmdLen,
IN EFI_USB_DATA_DIRECTION DataDir,
IN UINT32 TransLen
)
{
USB_BOT_CBW Cbw;
EFI_STATUS Status;
UINT32 Result;
UINTN DataLen;
UINTN Timeout;
ASSERT ((CmdLen > 0) && (CmdLen <= USB_BOT_MAX_CMDLEN));
//
// Check is device been detached
//
if (mUsbCore->CheckDeviceDetached(UsbBot->UsbIo) == EFI_SUCCESS) {
return EFI_NOT_FOUND;
}
//
// Fill in the CSW.
//
Cbw.Signature = USB_BOT_CBW_SIGNATURE;
Cbw.Tag = UsbBot->CbwTag;
Cbw.DataLen = TransLen;
Cbw.Flag = ((DataDir == EfiUsbDataIn) ? 0x80 : 0);
Cbw.Lun = Cmd[1] >> 5;
Cbw.CmdLen = CmdLen;
ZeroMem (Cbw.CmdBlock, USB_BOT_MAX_CMDLEN);
CopyMem (Cbw.CmdBlock, Cmd, CmdLen);
Result = 0;
DataLen = sizeof (USB_BOT_CBW);
Timeout = USB_BOT_CBW_TIMEOUT / USB_MASS_STALL_1_MS;
//
// Some devices needs extremely long stall on first Inquery command at command phase
//
if (!UsbBot->FirstInquiryPassed && *(UINT8*)Cmd == 0x12 && !UsbBot->BypassCswTimeout) {
Timeout = USB_BOT_DATA_TIMEOUT / USB_MASS_STALL_1_MS;
}
//
// Use the UsbIo to send the command to the device. The default
// time out is enough.
//
Status = UsbBulkTransfer (
UsbBot->UsbIo,
UsbBot->BulkOutEndpoint->EndpointAddress,
&Cbw,
&DataLen,
Timeout,
&Result
);
//
// Respond to Bulk-Out endpoint stall with a Reset Recovery,
// see the spec section 5.3.1
//
if (EFI_ERROR (Status)) {
if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL) && DataDir == EfiUsbDataOut) {
UsbBotResetDevice (UsbBot, FALSE);
} else if (USB_IS_ERROR (Result, EFI_USB_ERR_NAK)) {
Status = EFI_NOT_READY;
} else if (Status == EFI_TIMEOUT) {
Status = EFI_NOT_FOUND;
}
}
return Status;
}
/**
Transfer the data between the device and host. BOT transfer
is composed of three phase, command, data, and status.
@param UsbBot The USB BOT device
@param DataDir The direction of the data
@param Data The buffer to hold data
@param TransLen The expected length of the data
@param Timeout The time to wait the command to complete
@param EFI_SUCCESS The data is transferred
@param Others Failed to transfer data
**/
STATIC
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
)
{
EFI_USB_ENDPOINT_DESCRIPTOR *Endpoint;
EFI_STATUS Status;
EFI_STATUS Status1;
UINT32 Result;
//
// Check is device been detached
//
if (mUsbCore->CheckDeviceDetached(UsbBot->UsbIo) == EFI_SUCCESS) {
return EFI_NOT_FOUND;
}
//
// It's OK if no data to transfer
//
if ((DataDir == EfiUsbNoData) || (*TransLen == 0)) {
return EFI_SUCCESS;
}
//
// Select the endpoint then issue the transfer
//
if (DataDir == EfiUsbDataIn) {
Endpoint = UsbBot->BulkInEndpoint;
} else {
Endpoint = UsbBot->BulkOutEndpoint;
}
Result = 0;
Timeout = Timeout / USB_MASS_STALL_1_MS;
Status = UsbBulkTransfer (
UsbBot->UsbIo,
Endpoint->EndpointAddress,
Data,
TransLen,
Timeout,
&Result
);
if (EFI_ERROR (Status)) {
DEBUG ((mUsbBotError, "UsbBotDataTransfer: (%r)\n", Status));
if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL) || (Status == EFI_TIMEOUT && UsbBot->BypassCswTimeout)) {
DEBUG ((mUsbBotError, "UsbBotDataTransfer: DataIn Stall\n"));
Status1 = mUsbCore->UsbClearDeviceFeature(
UsbBot->UsbIo,
USB_TARGET_ENDPOINT,
USB_FEATURE_ENDPOINT_HALT,
Endpoint->EndpointAddress,
&Result
);
if (EFI_ERROR (Status1)) {
//
// Serious error occurred, given EFI_NO_RESPONSE to skip status phase
//
Status = EFI_NO_RESPONSE;
}
//
// Restore flags when something wrong
//
UsbBot->FirstReadPassed = FALSE;
UsbBot->FirstReadCapPassed = FALSE;
UsbBot->FirstRequestSensePassed = FALSE;
UsbBot->FirstInquiryPassed = FALSE;
if (UsbBot->BypassCswTimeout) {
if (UsbBot->BypassCswTimeout == 1) {
return EFI_NOT_FOUND;
}
}
} else if (USB_IS_ERROR (Result, EFI_USB_ERR_NAK)) {
Status = EFI_NOT_READY;
}
}
return Status;
}
/**
Get the command execution status from device. BOT transfer is
composed of three phase, command, data, and status.
This function return the transfer status of the BOT's CSW status,
and return the high level command execution result in Result. So
even it returns EFI_SUCCESS, the command may still have failed.
@param UsbBot The USB BOT device
@param TransLen The expected length of the data
@param CmdStatus The result of the command execution.
@param Timeout The time to wait the command to complete
@retval EFI_SUCCESS Command execute result is retrieved and in the Result.
@retval EFI_DEVICE_ERROR Failed to retrieve the command execute result
**/
STATIC
EFI_STATUS
UsbBotGetStatus (
IN USB_BOT_PROTOCOL *UsbBot,
IN UINT32 TransLen,
OUT UINT8 *CmdStatus,
IN UINT32 Timeout
)
{
USB_BOT_CSW Csw;
UINTN Len;
UINT8 Endpoint;
EFI_STATUS Status;
UINT32 Result;
EFI_USB_IO_PROTOCOL *UsbIo;
UINT32 Index;
*CmdStatus = USB_BOT_COMMAND_ERROR;
Status = EFI_DEVICE_ERROR;
Endpoint = UsbBot->BulkInEndpoint->EndpointAddress;
UsbIo = UsbBot->UsbIo;
Timeout = (Timeout / USB_MASS_STALL_1_MS) / USB_BOT_GET_STATUS_RETRY;
for (Index = 0; Index < USB_BOT_GET_STATUS_RETRY; Index ++) {
//
// Check is device been detached
//
if (mUsbCore->CheckDeviceDetached(UsbBot->UsbIo) == EFI_SUCCESS) {
return EFI_NOT_FOUND;
}
//
// Attemp to the read CSW from bulk in endpoint
//
ZeroMem (&Csw, sizeof (USB_BOT_CSW));
Result = 0;
Len = sizeof (USB_BOT_CSW);
Status = UsbBulkTransfer (
UsbIo,
Endpoint,
&Csw,
&Len,
Timeout,
&Result
);
if (EFI_ERROR(Status)) {
DEBUG ((mUsbBotError, "UsbBotGetStatus (%r)\n", Status));
if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL) || (Status == EFI_TIMEOUT && UsbBot->BypassCswTimeout)) {
DEBUG ((mUsbBotError, "UsbBotGetStatus: DataIn Stall\n"));
mUsbCore->UsbClearDeviceFeature(
UsbBot->UsbIo,
USB_TARGET_ENDPOINT,
USB_FEATURE_ENDPOINT_HALT,
Endpoint,
&Result
);
//
// Restore flags when something wrong
//
UsbBot->FirstReadPassed = FALSE;
UsbBot->FirstReadCapPassed = FALSE;
UsbBot->FirstRequestSensePassed = FALSE;
UsbBot->FirstInquiryPassed = FALSE;
if (UsbBot->BypassCswTimeout) {
if (UsbBot->BypassCswTimeout == 1) {
return EFI_NOT_FOUND;
}
break;
}
}
continue;
}
//
// Following situations will be treated as valid CSW
// 1. Signature not equal to "USBS" but the tag equal to CBW's tag
// 2. Both of signature and tag equal to zero
//
if (Len != sizeof (USB_BOT_CSW)) {
//
// Something wrong, return command error
//
*CmdStatus = USB_MASS_CMD_PHASE_ERROR;
} else if (Csw.Tag != UsbBot->CbwTag && !(Csw.Signature == 0 && Csw.Tag == 0)) {
//
// Invalid Csw need perform reset recovery
//
DEBUG ((mUsbBotError, "UsbBotGetStatus: Device return a invalid signature\n"));
Status = UsbBotResetDevice (UsbBot, FALSE);
} else if (Csw.CmdStatus == USB_BOT_COMMAND_ERROR) {
//
// Respond phase error need perform reset recovery
//
DEBUG ((mUsbBotError, "UsbBotGetStatus: Device return a phase error\n"));
Status = UsbBotResetDevice (UsbBot, FALSE);
*CmdStatus = Csw.CmdStatus;
} else {
*CmdStatus = Csw.CmdStatus;
}
break;
}
//
//The tag is increased even there is an error.
//
UsbBot->CbwTag++;
return Status;
}
/**
Call the Usb mass storage class transport protocol to issue
the command/data/status circle to execute the commands
@param Context The context of the BOT protocol, that is, USB_BOT_PROTOCOL
@param Cmd The high level command
@param CmdLen The command length
@param DataDir The direction of the data transfer
@param Data The buffer to hold data
@param DataLen The length of the data
@param Timeout The time to wait command
@param CmdStatus The result of high level command execution
@retval EFI_SUCCESS The command is executed OK, and result in CmdStatus
@retval EFI_DEVICE_ERROR Failed to excute command
**/
STATIC
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 UINT32 Timeout,
OUT UINT32 *CmdStatus
)
{
USB_BOT_PROTOCOL *UsbBot;
EFI_STATUS Status;
UINTN TransLen;
UINT8 Result;
*CmdStatus = USB_MASS_CMD_FAIL;
UsbBot = (USB_BOT_PROTOCOL *) Context;
//
// Send the command to the device. Return immediately if device
// rejects the command.
//
Status = UsbBotSendCommand (UsbBot, Cmd, CmdLen, DataDir, DataLen);
if (EFI_ERROR (Status)) {
DEBUG ((mUsbBotError, "UsbBotExecCommand: UsbBotSendCommand (%r)\n", Status));
return Status;
}
//
// Some devices needs extremely long stall on first Read10 command when
// the SATA "Standby Immediately" been set to the device
//
if (!UsbBot->FirstReadPassed && *(UINT8*)Cmd == 0x28) {
if (Timeout < USB_BOT_DATA_TIMEOUT) Timeout = USB_BOT_DATA_TIMEOUT;
UsbBot->FirstReadPassed = TRUE;
}
//
// Some ODD devices needs extremely long stall on first Read Capacity command
//
if (!UsbBot->FirstReadCapPassed && *(UINT8*)Cmd == 0x25) {
if (Timeout < USB_BOT_DATA_TIMEOUT) Timeout = USB_BOT_DATA_TIMEOUT;
UsbBot->FirstReadCapPassed = TRUE;
}
//
// Some devices needs extremely long stall on first RequestSense command when
// last command got Command Failed result
//
if (!UsbBot->FirstRequestSensePassed && *(UINT8*)Cmd == 0x03) {
if (Timeout < USB_BOT_DATA_TIMEOUT) Timeout = USB_BOT_DATA_TIMEOUT;
UsbBot->FirstRequestSensePassed = TRUE;
}
//
// Some devices needs extremely long stall on first Inquery command when
// last command got Command Failed result
//
if (!UsbBot->FirstInquiryPassed && *(UINT8*)Cmd == 0x12 && !UsbBot->BypassCswTimeout) {
if (Timeout < USB_BOT_DATA_TIMEOUT) Timeout = USB_BOT_DATA_TIMEOUT;
UsbBot->FirstInquiryPassed = TRUE;
}
//
// Transfer the data. Don't return immediately even data transfer
// failed. The host should attempt to receive the CSW no matter
// whether it succeeds or failed.
//
TransLen = (UINTN) DataLen;
Status = UsbBotDataTransfer (UsbBot, DataDir, Data, &TransLen, Timeout);
if (Status == EFI_NOT_FOUND) {
return Status;
}
//
// No response on data phase, skip status phase to save time on retry
//
if (Status == EFI_NO_RESPONSE) {
*CmdStatus = USB_MASS_CMD_PHASE_ERROR;
return Status;
}
//
// Issue port reset if we got timeout error on Inquiry command to workaround some incompatible HDD
//
if (Status == EFI_TIMEOUT && *(UINT8*)Cmd == 0x12) {
UsbBotResetDevice (UsbBot, TRUE);
return Status;
}
//
// Some devices needs extremely long stall on first BOT command to waiting
// for firmware startup during cold boot
//
Timeout = USB_BOT_CSW_TIMEOUT;
if (UsbBot->CbwTag == 1 && !UsbBot->BypassCswTimeout) {
Timeout = USB_BOT_CSW_TIMEOUT * 2;
}
//
// Get the status, if that succeeds, interpret the result
//
Status = UsbBotGetStatus (UsbBot, DataLen, &Result, Timeout);
if (EFI_ERROR (Status)) {
DEBUG ((mUsbBotError, "UsbBotExecCommand: UsbBotGetStatus (%r)\n", Status));
return Status;
}
if (Result == 0) {
*CmdStatus = USB_MASS_CMD_SUCCESS;
} else if (Result == USB_MASS_CMD_PHASE_ERROR) {
*CmdStatus = USB_MASS_CMD_PHASE_ERROR;
}
return EFI_SUCCESS;
}
/**
Reset the mass storage device by BOT protocol
@param Context The context of the BOT protocol, that is, USB_BOT_PROTOCOL
@param EFI_SUCCESS The device is reset
@param Others Failed to reset the device.
**/
STATIC
EFI_STATUS
UsbBotResetDevice (
IN VOID *Context,
IN BOOLEAN ExtendedVerification
)
{
USB_BOT_PROTOCOL *UsbBot;
EFI_USB_DEVICE_REQUEST Request;
EFI_STATUS Status;
UINT32 Result;
UINT32 Timeout;
UsbBot = (USB_BOT_PROTOCOL *) Context;
//
// Check is device been detached
//
if (mUsbCore->CheckDeviceDetached(UsbBot->UsbIo) == EFI_SUCCESS) {
return EFI_NOT_FOUND;
}
if (ExtendedVerification) {
//
// If we need to do strictly reset, reset its parent hub port
//
Status = UsbPortReset (UsbBot->UsbIo);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Issue a class specific "Bulk-Only Mass Storage Reset reqest.
// See the spec section 3.1
//
Request.RequestType = 0x21;
Request.Request = USB_BOT_RESET_REQUEST;
Request.Value = 0;
Request.Index = UsbBot->Interface.InterfaceNumber;
Request.Length = 0;
Timeout = USB_BOT_RESET_TIMEOUT / USB_MASS_STALL_1_MS;
Status = UsbControlTransfer (
UsbBot->UsbIo,
&Request,
EfiUsbNoData,
Timeout,
NULL,
0,
&Result
);
if (EFI_ERROR (Status)) {
DEBUG ((mUsbBotError, "UsbBotResetDevice: (%r)\n", Status));
return Status;
}
//
// The device shall NAK the host's request until the reset is
// complete. We can use this to sync the device and host. For
// now just stall 100ms to wait the device.
//
mUsbCore->Stall (USB_BOT_RESET_STALL);
//
// Clear the Bulk-In and Bulk-Out stall condition.
//
mUsbCore->UsbClearDeviceFeature(
UsbBot->UsbIo,
USB_TARGET_ENDPOINT,
USB_FEATURE_ENDPOINT_HALT,
UsbBot->BulkInEndpoint->EndpointAddress,
&Result
);
//
// A stall between Bulk-In and Bulk-Out for some smart phone device
//
mUsbCore->Stall (USB_BOT_RESET_STALL / 2);
mUsbCore->UsbClearDeviceFeature(
UsbBot->UsbIo,
USB_TARGET_ENDPOINT,
USB_FEATURE_ENDPOINT_HALT,
UsbBot->BulkOutEndpoint->EndpointAddress,
&Result
);
return Status;
}
/**
Clean up the resource used by this BOT protocol
@param Context The context of the BOT protocol, that is, USB_BOT_PROTOCOL
@retval EFI_SUCCESS The resource is cleaned up.
**/
STATIC
EFI_STATUS
UsbBotFini (
IN VOID *Context
)
{
USB_BOT_PROTOCOL *UsbBot;
UsbBot = (USB_BOT_PROTOCOL *) Context;
mUsbCore->FreeBuffer (
sizeof (USB_BOT_PROTOCOL) + 2 * sizeof (EFI_USB_ENDPOINT_DESCRIPTOR),
UsbBot
);
//
// Remove Smm address convert table
//
mUsbCore->RemoveAddressConvertTable (
ACT_FUNCTION_POINTER,
&UsbBot->Transport.Init
);
mUsbCore->RemoveAddressConvertTable (
ACT_INSTANCE_BODY,
UsbBot
);
mUsbCore->RemoveAddressConvertTable (
ACT_INSTANCE_POINTER,
&UsbBot->BulkInEndpoint
);
mUsbCore->RemoveAddressConvertTable (
ACT_INSTANCE_POINTER,
&UsbBot->BulkOutEndpoint
);
mUsbCore->RemoveAddressConvertTable (
ACT_INSTANCE_POINTER,
&UsbBot->UsbIo
);
return EFI_SUCCESS;
}