diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassBoot.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassBoot.c | 998 |
1 files changed, 998 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassBoot.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassBoot.c new file mode 100644 index 00000000..a4a2c63b --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassBoot.c @@ -0,0 +1,998 @@ +/** @file + Implementation of the command set of USB Mass Storage Specification + for Bootability, Revision 1.0. + +Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "UsbMass.h" + +/** + Execute REQUEST SENSE Command to retrieve sense data from device. + + @param UsbMass The device whose sense data is requested. + + @retval EFI_SUCCESS The command is executed successfully. + @retval EFI_DEVICE_ERROR Failed to request sense. + @retval EFI_NO_RESPONSE The device media doesn't response this request. + @retval EFI_INVALID_PARAMETER The command has some invalid parameters. + @retval EFI_WRITE_PROTECTED The device is write protected. + @retval EFI_MEDIA_CHANGED The device media has been changed. + +**/ +EFI_STATUS +UsbBootRequestSense ( + IN USB_MASS_DEVICE *UsbMass + ) +{ + USB_BOOT_REQUEST_SENSE_CMD SenseCmd; + USB_BOOT_REQUEST_SENSE_DATA SenseData; + EFI_BLOCK_IO_MEDIA *Media; + USB_MASS_TRANSPORT *Transport; + EFI_STATUS Status; + UINT32 CmdResult; + + Transport = UsbMass->Transport; + + // + // Request the sense data from the device + // + ZeroMem (&SenseCmd, sizeof (USB_BOOT_REQUEST_SENSE_CMD)); + ZeroMem (&SenseData, sizeof (USB_BOOT_REQUEST_SENSE_DATA)); + + SenseCmd.OpCode = USB_BOOT_REQUEST_SENSE_OPCODE; + SenseCmd.Lun = (UINT8) (USB_BOOT_LUN (UsbMass->Lun)); + SenseCmd.AllocLen = (UINT8) sizeof (USB_BOOT_REQUEST_SENSE_DATA); + + Status = Transport->ExecCommand ( + UsbMass->Context, + &SenseCmd, + sizeof (USB_BOOT_REQUEST_SENSE_CMD), + EfiUsbDataIn, + &SenseData, + sizeof (USB_BOOT_REQUEST_SENSE_DATA), + UsbMass->Lun, + USB_BOOT_GENERAL_CMD_TIMEOUT, + &CmdResult + ); + if (EFI_ERROR (Status) || CmdResult != USB_MASS_CMD_SUCCESS) { + DEBUG ((EFI_D_ERROR, "UsbBootRequestSense: (%r) CmdResult=0x%x\n", Status, CmdResult)); + if (!EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + } + return Status; + } + + // + // If sense data is retrieved successfully, interpret the sense data + // and update the media status if necessary. + // + Media = &UsbMass->BlockIoMedia; + + switch (USB_BOOT_SENSE_KEY (SenseData.SenseKey)) { + + case USB_BOOT_SENSE_NO_SENSE: + if (SenseData.Asc == USB_BOOT_ASC_NO_ADDITIONAL_SENSE_INFORMATION) { + // + // It is not an error if a device does not have additional sense information + // + Status = EFI_SUCCESS; + } else { + Status = EFI_NO_RESPONSE; + } + break; + + case USB_BOOT_SENSE_RECOVERED: + // + // Suppose hardware can handle this case, and recover later by itself + // + Status = EFI_NOT_READY; + break; + + case USB_BOOT_SENSE_NOT_READY: + Status = EFI_DEVICE_ERROR; + if (SenseData.Asc == USB_BOOT_ASC_NO_MEDIA) { + Media->MediaPresent = FALSE; + Status = EFI_NO_MEDIA; + } else if (SenseData.Asc == USB_BOOT_ASC_NOT_READY) { + Status = EFI_NOT_READY; + } + break; + + case USB_BOOT_SENSE_ILLEGAL_REQUEST: + Status = EFI_INVALID_PARAMETER; + break; + + case USB_BOOT_SENSE_UNIT_ATTENTION: + Status = EFI_DEVICE_ERROR; + if (SenseData.Asc == USB_BOOT_ASC_MEDIA_CHANGE) { + // + // If MediaChange, reset ReadOnly and new MediaId + // + Status = EFI_MEDIA_CHANGED; + Media->ReadOnly = FALSE; + Media->MediaId++; + } else if (SenseData.Asc == USB_BOOT_ASC_NOT_READY) { + Status = EFI_NOT_READY; + } else if (SenseData.Asc == USB_BOOT_ASC_NO_MEDIA) { + Status = EFI_NOT_READY; + } + break; + + case USB_BOOT_SENSE_DATA_PROTECT: + Status = EFI_WRITE_PROTECTED; + Media->ReadOnly = TRUE; + break; + + default: + Status = EFI_DEVICE_ERROR; + break; + } + + DEBUG ((EFI_D_INFO, "UsbBootRequestSense: (%r) with error code (%x) sense key %x/%x/%x\n", + Status, + SenseData.ErrorCode, + USB_BOOT_SENSE_KEY (SenseData.SenseKey), + SenseData.Asc, + SenseData.Ascq + )); + + return Status; +} + + +/** + Execute the USB mass storage bootability commands. + + This function executes the USB mass storage bootability commands. + If execution failed, retrieve the error by REQUEST_SENSE, then + update the device's status, such as ReadyOnly. + + @param UsbMass The device to issue commands to + @param Cmd The command to execute + @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 expected data + @param Timeout The timeout used to transfer + + @retval EFI_SUCCESS Command is executed successfully + @retval Others Command execution failed. + +**/ +EFI_STATUS +UsbBootExecCmd ( + IN USB_MASS_DEVICE *UsbMass, + IN VOID *Cmd, + IN UINT8 CmdLen, + IN EFI_USB_DATA_DIRECTION DataDir, + IN VOID *Data, + IN UINT32 DataLen, + IN UINT32 Timeout + ) +{ + USB_MASS_TRANSPORT *Transport; + EFI_STATUS Status; + UINT32 CmdResult; + + Transport = UsbMass->Transport; + Status = Transport->ExecCommand ( + UsbMass->Context, + Cmd, + CmdLen, + DataDir, + Data, + DataLen, + UsbMass->Lun, + Timeout, + &CmdResult + ); + + if (Status == EFI_TIMEOUT) { + DEBUG ((EFI_D_ERROR, "UsbBootExecCmd: %r to Exec 0x%x Cmd\n", Status, *(UINT8 *)Cmd)); + return EFI_TIMEOUT; + } + + // + // If ExecCommand() returns no error and CmdResult is success, + // then the command transfer is successful. + // + if ((CmdResult == USB_MASS_CMD_SUCCESS) && !EFI_ERROR (Status)) { + return EFI_SUCCESS; + } + + // + // If command execution failed, then retrieve error info via sense request. + // + DEBUG ((EFI_D_ERROR, "UsbBootExecCmd: %r to Exec 0x%x Cmd (Result = %x)\n", Status, *(UINT8 *)Cmd, CmdResult)); + return UsbBootRequestSense (UsbMass); +} + + +/** + Execute the USB mass storage bootability commands with retrial. + + This function executes USB mass storage bootability commands. + If the device isn't ready, wait for it. If the device is ready + and error occurs, retry the command again until it exceeds the + limit of retrial times. + + @param UsbMass The device to issue commands to + @param Cmd The command to execute + @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 expected data + @param Timeout The timeout used to transfer + + @retval EFI_SUCCESS The command is executed successfully. + @retval EFI_NO_MEDIA The device media is removed. + @retval Others Command execution failed after retrial. + +**/ +EFI_STATUS +UsbBootExecCmdWithRetry ( + IN USB_MASS_DEVICE *UsbMass, + IN VOID *Cmd, + IN UINT8 CmdLen, + IN EFI_USB_DATA_DIRECTION DataDir, + IN VOID *Data, + IN UINT32 DataLen, + IN UINT32 Timeout + ) +{ + EFI_STATUS Status; + UINTN Retry; + EFI_EVENT TimeoutEvt; + + Retry = 0; + Status = EFI_SUCCESS; + Status = gBS->CreateEvent ( + EVT_TIMER, + TPL_CALLBACK, + NULL, + NULL, + &TimeoutEvt + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gBS->SetTimer (TimeoutEvt, TimerRelative, EFI_TIMER_PERIOD_SECONDS(60)); + if (EFI_ERROR (Status)) { + goto EXIT; + } + + // + // Execute the cmd and retry if it fails. + // + while (EFI_ERROR (gBS->CheckEvent (TimeoutEvt))) { + Status = UsbBootExecCmd ( + UsbMass, + Cmd, + CmdLen, + DataDir, + Data, + DataLen, + Timeout + ); + if (Status == EFI_SUCCESS || Status == EFI_NO_MEDIA) { + break; + } + // + // If the sense data shows the drive is not ready, we need execute the cmd again. + // We limit the upper boundary to 60 seconds. + // + if (Status == EFI_NOT_READY) { + continue; + } + // + // If the status is other error, then just retry 5 times. + // + if (Retry++ >= USB_BOOT_COMMAND_RETRY) { + break; + } + } + +EXIT: + if (TimeoutEvt != NULL) { + gBS->CloseEvent (TimeoutEvt); + } + + return Status; +} + + +/** + Execute TEST UNIT READY command to check if the device is ready. + + @param UsbMass The device to test + + @retval EFI_SUCCESS The device is ready. + @retval Others Device not ready. + +**/ +EFI_STATUS +UsbBootIsUnitReady ( + IN USB_MASS_DEVICE *UsbMass + ) +{ + USB_BOOT_TEST_UNIT_READY_CMD TestCmd; + + ZeroMem (&TestCmd, sizeof (USB_BOOT_TEST_UNIT_READY_CMD)); + + TestCmd.OpCode = USB_BOOT_TEST_UNIT_READY_OPCODE; + TestCmd.Lun = (UINT8) (USB_BOOT_LUN (UsbMass->Lun)); + + return UsbBootExecCmdWithRetry ( + UsbMass, + &TestCmd, + (UINT8) sizeof (USB_BOOT_TEST_UNIT_READY_CMD), + EfiUsbNoData, + NULL, + 0, + USB_BOOT_GENERAL_CMD_TIMEOUT + ); +} + + +/** + Execute INQUIRY Command to request information regarding parameters of + the device be sent to the host computer. + + @param UsbMass The device to inquire. + + @retval EFI_SUCCESS INQUIRY Command is executed successfully. + @retval Others INQUIRY Command is not executed successfully. + +**/ +EFI_STATUS +UsbBootInquiry ( + IN USB_MASS_DEVICE *UsbMass + ) +{ + USB_BOOT_INQUIRY_CMD InquiryCmd; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + + Media = &(UsbMass->BlockIoMedia); + + ZeroMem (&InquiryCmd, sizeof (USB_BOOT_INQUIRY_CMD)); + ZeroMem (&UsbMass->InquiryData, sizeof (USB_BOOT_INQUIRY_DATA)); + + InquiryCmd.OpCode = USB_BOOT_INQUIRY_OPCODE; + InquiryCmd.Lun = (UINT8) (USB_BOOT_LUN (UsbMass->Lun)); + InquiryCmd.AllocLen = (UINT8) sizeof (USB_BOOT_INQUIRY_DATA); + + Status = UsbBootExecCmdWithRetry ( + UsbMass, + &InquiryCmd, + (UINT8) sizeof (USB_BOOT_INQUIRY_CMD), + EfiUsbDataIn, + &UsbMass->InquiryData, + sizeof (USB_BOOT_INQUIRY_DATA), + USB_BOOT_GENERAL_CMD_TIMEOUT + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Get information from PDT (Peripheral Device Type) field and Removable Medium Bit + // from the inquiry data. + // + UsbMass->Pdt = (UINT8) (USB_BOOT_PDT (UsbMass->InquiryData.Pdt)); + Media->RemovableMedia = (BOOLEAN) (USB_BOOT_REMOVABLE (UsbMass->InquiryData.Removable)); + // + // Set block size to the default value of 512 Bytes, in case no media is present at first time. + // + Media->BlockSize = 0x0200; + + return Status; +} + +/** + Execute READ CAPACITY 16 bytes command to request information regarding + the capacity of the installed medium of the device. + + This function executes READ CAPACITY 16 bytes command to get the capacity + of the USB mass storage media, including the presence, block size, + and last block number. + + @param UsbMass The device to retireve disk gemotric. + + @retval EFI_SUCCESS The disk geometry is successfully retrieved. + @retval EFI_NOT_READY The returned block size is zero. + @retval Other READ CAPACITY 16 bytes command execution failed. + +**/ +EFI_STATUS +UsbBootReadCapacity16 ( + IN USB_MASS_DEVICE *UsbMass + ) +{ + UINT8 CapacityCmd[16]; + EFI_SCSI_DISK_CAPACITY_DATA16 CapacityData; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + UINT32 BlockSize; + + Media = &UsbMass->BlockIoMedia; + + Media->MediaPresent = FALSE; + Media->LastBlock = 0; + Media->BlockSize = 0; + + ZeroMem (CapacityCmd, sizeof (CapacityCmd)); + ZeroMem (&CapacityData, sizeof (CapacityData)); + + CapacityCmd[0] = EFI_SCSI_OP_READ_CAPACITY16; + CapacityCmd[1] = 0x10; + // + // Partial medium indicator, set the bytes 2 ~ 9 of the Cdb as ZERO. + // + ZeroMem ((CapacityCmd + 2), 8); + + CapacityCmd[13] = sizeof (CapacityData); + + Status = UsbBootExecCmdWithRetry ( + UsbMass, + CapacityCmd, + (UINT8) sizeof (CapacityCmd), + EfiUsbDataIn, + &CapacityData, + sizeof (CapacityData), + USB_BOOT_GENERAL_CMD_TIMEOUT + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Get the information on media presence, block size, and last block number + // from READ CAPACITY data. + // + Media->MediaPresent = TRUE; + Media->LastBlock = SwapBytes64 (ReadUnaligned64 ((CONST UINT64 *) &(CapacityData.LastLba7))); + + BlockSize = SwapBytes32 (ReadUnaligned32 ((CONST UINT32 *) &(CapacityData.BlockSize3))); + + Media->LowestAlignedLba = (CapacityData.LowestAlignLogic2 << 8) | + CapacityData.LowestAlignLogic1; + Media->LogicalBlocksPerPhysicalBlock = (1 << CapacityData.LogicPerPhysical); + if (BlockSize == 0) { + // + // Get sense data + // + return UsbBootRequestSense (UsbMass); + } else { + Media->BlockSize = BlockSize; + } + + return Status; +} + + +/** + Execute READ CAPACITY command to request information regarding + the capacity of the installed medium of the device. + + This function executes READ CAPACITY command to get the capacity + of the USB mass storage media, including the presence, block size, + and last block number. + + @param UsbMass The device to retireve disk gemotric. + + @retval EFI_SUCCESS The disk geometry is successfully retrieved. + @retval EFI_NOT_READY The returned block size is zero. + @retval Other READ CAPACITY command execution failed. + +**/ +EFI_STATUS +UsbBootReadCapacity ( + IN USB_MASS_DEVICE *UsbMass + ) +{ + USB_BOOT_READ_CAPACITY_CMD CapacityCmd; + USB_BOOT_READ_CAPACITY_DATA CapacityData; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + UINT32 BlockSize; + + Media = &UsbMass->BlockIoMedia; + + ZeroMem (&CapacityCmd, sizeof (USB_BOOT_READ_CAPACITY_CMD)); + ZeroMem (&CapacityData, sizeof (USB_BOOT_READ_CAPACITY_DATA)); + + CapacityCmd.OpCode = USB_BOOT_READ_CAPACITY_OPCODE; + CapacityCmd.Lun = (UINT8) (USB_BOOT_LUN (UsbMass->Lun)); + + Status = UsbBootExecCmdWithRetry ( + UsbMass, + &CapacityCmd, + (UINT8) sizeof (USB_BOOT_READ_CAPACITY_CMD), + EfiUsbDataIn, + &CapacityData, + sizeof (USB_BOOT_READ_CAPACITY_DATA), + USB_BOOT_GENERAL_CMD_TIMEOUT + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Get the information on media presence, block size, and last block number + // from READ CAPACITY data. + // + Media->MediaPresent = TRUE; + Media->LastBlock = SwapBytes32 (ReadUnaligned32 ((CONST UINT32 *) CapacityData.LastLba)); + + BlockSize = SwapBytes32 (ReadUnaligned32 ((CONST UINT32 *) CapacityData.BlockLen)); + if (BlockSize == 0) { + // + // Get sense data + // + return UsbBootRequestSense (UsbMass); + } else { + Media->BlockSize = BlockSize; + } + + if (Media->LastBlock == 0xFFFFFFFF) { + Status = UsbBootReadCapacity16 (UsbMass); + if (!EFI_ERROR (Status)) { + UsbMass->Cdb16Byte = TRUE; + } + } + + return Status; +} + +/** + Retrieves SCSI mode sense information via MODE SENSE(6) command. + + @param UsbMass The device whose sense data is requested. + + @retval EFI_SUCCESS SCSI mode sense information retrieved successfully. + @retval Other Command execution failed. + +**/ +EFI_STATUS +UsbScsiModeSense ( + IN USB_MASS_DEVICE *UsbMass + ) +{ + EFI_STATUS Status; + USB_SCSI_MODE_SENSE6_CMD ModeSenseCmd; + USB_SCSI_MODE_SENSE6_PARA_HEADER ModeParaHeader; + EFI_BLOCK_IO_MEDIA *Media; + + Media = &UsbMass->BlockIoMedia; + + ZeroMem (&ModeSenseCmd, sizeof (USB_SCSI_MODE_SENSE6_CMD)); + ZeroMem (&ModeParaHeader, sizeof (USB_SCSI_MODE_SENSE6_PARA_HEADER)); + + // + // MODE SENSE(6) command is defined in Section 8.2.10 of SCSI-2 Spec + // + ModeSenseCmd.OpCode = USB_SCSI_MODE_SENSE6_OPCODE; + ModeSenseCmd.Lun = (UINT8) USB_BOOT_LUN (UsbMass->Lun); + ModeSenseCmd.PageCode = 0x3F; + ModeSenseCmd.AllocateLen = (UINT8) sizeof (USB_SCSI_MODE_SENSE6_PARA_HEADER); + + Status = UsbBootExecCmdWithRetry ( + UsbMass, + &ModeSenseCmd, + (UINT8) sizeof (USB_SCSI_MODE_SENSE6_CMD), + EfiUsbDataIn, + &ModeParaHeader, + sizeof (USB_SCSI_MODE_SENSE6_PARA_HEADER), + USB_BOOT_GENERAL_CMD_TIMEOUT + ); + + // + // Format of device-specific parameter byte of the mode parameter header is defined in + // Section 8.2.10 of SCSI-2 Spec. + // BIT7 of this byte is indicates whether the medium is write protected. + // + if (!EFI_ERROR (Status)) { + Media->ReadOnly = (BOOLEAN) ((ModeParaHeader.DevicePara & BIT7) != 0); + } + + return Status; +} + + +/** + Get the parameters for the USB mass storage media. + + This function get the parameters for the USB mass storage media, + It is used both to initialize the media during the Start() phase + of Driver Binding Protocol and to re-initialize it when the media is + changed. Although the RemoveableMedia is unlikely to change, + it is also included here. + + @param UsbMass The device to retrieve disk gemotric. + + @retval EFI_SUCCESS The disk gemotric is successfully retrieved. + @retval Other Failed to get the parameters. + +**/ +EFI_STATUS +UsbBootGetParams ( + IN USB_MASS_DEVICE *UsbMass + ) +{ + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + + Media = &(UsbMass->BlockIoMedia); + + Status = UsbBootInquiry (UsbMass); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "UsbBootGetParams: UsbBootInquiry (%r)\n", Status)); + return Status; + } + + // + // According to USB Mass Storage Specification for Bootability, only following + // 4 Peripheral Device Types are in spec. + // + if ((UsbMass->Pdt != USB_PDT_DIRECT_ACCESS) && + (UsbMass->Pdt != USB_PDT_CDROM) && + (UsbMass->Pdt != USB_PDT_OPTICAL) && + (UsbMass->Pdt != USB_PDT_SIMPLE_DIRECT)) { + DEBUG ((EFI_D_ERROR, "UsbBootGetParams: Found an unsupported peripheral type[%d]\n", UsbMass->Pdt)); + return EFI_UNSUPPORTED; + } + + // + // Don't use the Removable bit in inquiry data to test whether the media + // is removable because many flash disks wrongly set this bit. + // + if ((UsbMass->Pdt == USB_PDT_CDROM) || (UsbMass->Pdt == USB_PDT_OPTICAL)) { + // + // CD-Rom device and Non-CD optical device + // + UsbMass->OpticalStorage = TRUE; + // + // Default value 2048 Bytes, in case no media present at first time + // + Media->BlockSize = 0x0800; + } + + Status = UsbBootDetectMedia (UsbMass); + + return Status; +} + + +/** + Detect whether the removable media is present and whether it has changed. + + @param UsbMass The device to check. + + @retval EFI_SUCCESS The media status is successfully checked. + @retval Other Failed to detect media. + +**/ +EFI_STATUS +UsbBootDetectMedia ( + IN USB_MASS_DEVICE *UsbMass + ) +{ + EFI_BLOCK_IO_MEDIA OldMedia; + EFI_BLOCK_IO_MEDIA *Media; + UINT8 CmdSet; + EFI_STATUS Status; + + Media = &UsbMass->BlockIoMedia; + + CopyMem (&OldMedia, &(UsbMass->BlockIoMedia), sizeof (EFI_BLOCK_IO_MEDIA)); + + CmdSet = ((EFI_USB_INTERFACE_DESCRIPTOR *) (UsbMass->Context))->InterfaceSubClass; + + Status = UsbBootIsUnitReady (UsbMass); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "UsbBootDetectMedia: UsbBootIsUnitReady (%r)\n", Status)); + } + + // + // Status could be: + // EFI_SUCCESS: all good. + // EFI_NO_MEDIA: media is not present. + // others: HW error. + // For either EFI_NO_MEDIA, or HW error, skip to get WriteProtected and capacity information. + // + if (!EFI_ERROR (Status)) { + if ((UsbMass->Pdt != USB_PDT_CDROM) && (CmdSet == USB_MASS_STORE_SCSI)) { + // + // MODE SENSE is required for the device with PDT of 0x00/0x07/0x0E, + // according to Section 4 of USB Mass Storage Specification for Bootability. + // MODE SENSE(10) is useless here, while MODE SENSE(6) defined in SCSI + // could get the information of Write Protected. + // Since not all device support this command, skip if fail. + // + UsbScsiModeSense (UsbMass); + } + + Status = UsbBootReadCapacity (UsbMass); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "UsbBootDetectMedia: UsbBootReadCapacity (%r)\n", Status)); + } + } + + if (EFI_ERROR (Status) && Status != EFI_NO_MEDIA) { + // + // For NoMedia, BlockIo is still needed. + // + return Status; + } + + // + // Simply reject device whose block size is unacceptable small (==0) or large (>64K). + // + if ((Media->BlockSize == 0) || (Media->BlockSize > USB_BOOT_MAX_CARRY_SIZE)) { + return EFI_DEVICE_ERROR; + } + + // + // Detect whether it is necessary to reinstall the Block I/O Protocol. + // + // MediaId may change in RequestSense for MediaChanged + // MediaPresent may change in RequestSense for NoMedia + // MediaReadOnly may change in RequestSense for WriteProtected or MediaChanged + // MediaPresent/BlockSize/LastBlock may change in ReadCapacity + // + if ((Media->MediaId != OldMedia.MediaId) || + (Media->MediaPresent != OldMedia.MediaPresent) || + (Media->ReadOnly != OldMedia.ReadOnly) || + (Media->BlockSize != OldMedia.BlockSize) || + (Media->LastBlock != OldMedia.LastBlock)) { + + // + // This function is called from: + // Block I/O Protocol APIs, which run at TPL_CALLBACK. + // DriverBindingStart(), which raises to TPL_CALLBACK. + ASSERT (EfiGetCurrentTpl () == TPL_CALLBACK); + + // + // When it is called from DriverBindingStart(), below reinstall fails. + // So ignore the return status check. + // + gBS->ReinstallProtocolInterface ( + UsbMass->Controller, + &gEfiBlockIoProtocolGuid, + &UsbMass->BlockIo, + &UsbMass->BlockIo + ); + + // + // Reset MediaId after reinstalling Block I/O Protocol. + // + if (Media->MediaPresent != OldMedia.MediaPresent) { + if (Media->MediaPresent) { + Media->MediaId = 1; + } else { + Media->MediaId = 0; + } + } + + if ((Media->ReadOnly != OldMedia.ReadOnly) || + (Media->BlockSize != OldMedia.BlockSize) || + (Media->LastBlock != OldMedia.LastBlock)) { + Media->MediaId++; + } + + Status = Media->MediaPresent ? EFI_MEDIA_CHANGED : EFI_NO_MEDIA; + } + + return Status; +} + + +/** + Read or write some blocks from the device. + + @param UsbMass The USB mass storage device to access + @param Write TRUE for write operation. + @param Lba The start block number + @param TotalBlock Total block number to read or write + @param Buffer The buffer to read to or write from + + @retval EFI_SUCCESS Data are read into the buffer or writen into the device. + @retval Others Failed to read or write all the data + +**/ +EFI_STATUS +UsbBootReadWriteBlocks ( + IN USB_MASS_DEVICE *UsbMass, + IN BOOLEAN Write, + IN UINT32 Lba, + IN UINTN TotalBlock, + IN OUT UINT8 *Buffer + ) +{ + USB_BOOT_READ_WRITE_10_CMD Cmd; + EFI_STATUS Status; + UINT32 Count; + UINT32 CountMax; + UINT32 BlockSize; + UINT32 ByteSize; + UINT32 Timeout; + + BlockSize = UsbMass->BlockIoMedia.BlockSize; + CountMax = USB_BOOT_MAX_CARRY_SIZE / BlockSize; + Status = EFI_SUCCESS; + + while (TotalBlock > 0) { + // + // Split the total blocks into smaller pieces to ease the pressure + // on the device. We must split the total block because the READ10 + // command only has 16 bit transfer length (in the unit of block). + // + Count = (UINT32)MIN (TotalBlock, CountMax); + Count = MIN (MAX_UINT16, Count); + ByteSize = Count * BlockSize; + + // + // USB command's upper limit timeout is 5s. [USB2.0-9.2.6.1] + // + Timeout = (UINT32) USB_BOOT_GENERAL_CMD_TIMEOUT; + + // + // Fill in the command then execute + // + ZeroMem (&Cmd, sizeof (USB_BOOT_READ_WRITE_10_CMD)); + + Cmd.OpCode = Write ? USB_BOOT_WRITE10_OPCODE : USB_BOOT_READ10_OPCODE; + Cmd.Lun = (UINT8) (USB_BOOT_LUN (UsbMass->Lun)); + WriteUnaligned32 ((UINT32 *) Cmd.Lba, SwapBytes32 (Lba)); + WriteUnaligned16 ((UINT16 *) Cmd.TransferLen, SwapBytes16 ((UINT16)Count)); + + Status = UsbBootExecCmdWithRetry ( + UsbMass, + &Cmd, + (UINT8) sizeof (USB_BOOT_READ_WRITE_10_CMD), + Write ? EfiUsbDataOut : EfiUsbDataIn, + Buffer, + ByteSize, + Timeout + ); + if (EFI_ERROR (Status)) { + return Status; + } + DEBUG (( + DEBUG_BLKIO, "UsbBoot%sBlocks: LBA (0x%lx), Blk (0x%x)\n", + Write ? L"Write" : L"Read", + Lba, Count + )); + Lba += Count; + Buffer += ByteSize; + TotalBlock -= Count; + } + + return Status; +} + +/** + Read or write some blocks from the device by SCSI 16 byte cmd. + + @param UsbMass The USB mass storage device to access + @param Write TRUE for write operation. + @param Lba The start block number + @param TotalBlock Total block number to read or write + @param Buffer The buffer to read to or write from + + @retval EFI_SUCCESS Data are read into the buffer or writen into the device. + @retval Others Failed to read or write all the data +**/ +EFI_STATUS +UsbBootReadWriteBlocks16 ( + IN USB_MASS_DEVICE *UsbMass, + IN BOOLEAN Write, + IN UINT64 Lba, + IN UINTN TotalBlock, + IN OUT UINT8 *Buffer + ) +{ + UINT8 Cmd[16]; + EFI_STATUS Status; + UINT32 Count; + UINT32 CountMax; + UINT32 BlockSize; + UINT32 ByteSize; + UINT32 Timeout; + + BlockSize = UsbMass->BlockIoMedia.BlockSize; + CountMax = USB_BOOT_MAX_CARRY_SIZE / BlockSize; + Status = EFI_SUCCESS; + + while (TotalBlock > 0) { + // + // Split the total blocks into smaller pieces. + // + Count = (UINT32)MIN (TotalBlock, CountMax); + ByteSize = Count * BlockSize; + + // + // USB command's upper limit timeout is 5s. [USB2.0-9.2.6.1] + // + Timeout = (UINT32) USB_BOOT_GENERAL_CMD_TIMEOUT; + + // + // Fill in the command then execute + // + ZeroMem (Cmd, sizeof (Cmd)); + + Cmd[0] = Write ? EFI_SCSI_OP_WRITE16 : EFI_SCSI_OP_READ16; + Cmd[1] = (UINT8) ((USB_BOOT_LUN (UsbMass->Lun) & 0xE0)); + WriteUnaligned64 ((UINT64 *) &Cmd[2], SwapBytes64 (Lba)); + WriteUnaligned32 ((UINT32 *) &Cmd[10], SwapBytes32 (Count)); + + Status = UsbBootExecCmdWithRetry ( + UsbMass, + Cmd, + (UINT8) sizeof (Cmd), + Write ? EfiUsbDataOut : EfiUsbDataIn, + Buffer, + ByteSize, + Timeout + ); + if (EFI_ERROR (Status)) { + return Status; + } + DEBUG (( + DEBUG_BLKIO, "UsbBoot%sBlocks16: LBA (0x%lx), Blk (0x%x)\n", + Write ? L"Write" : L"Read", + Lba, Count + )); + Lba += Count; + Buffer += ByteSize; + TotalBlock -= Count; + } + + return Status; +} + +/** + Use the USB clear feature control transfer to clear the endpoint stall condition. + + @param UsbIo The USB I/O Protocol instance + @param EndpointAddr The endpoint to clear stall for + + @retval EFI_SUCCESS The endpoint stall condition is cleared. + @retval Others Failed to clear the endpoint stall condition. + +**/ +EFI_STATUS +UsbClearEndpointStall ( + IN EFI_USB_IO_PROTOCOL *UsbIo, + IN UINT8 EndpointAddr + ) +{ + EFI_USB_DEVICE_REQUEST Request; + EFI_STATUS Status; + UINT32 CmdResult; + UINT32 Timeout; + + Request.RequestType = 0x02; + Request.Request = USB_REQ_CLEAR_FEATURE; + Request.Value = USB_FEATURE_ENDPOINT_HALT; + Request.Index = EndpointAddr; + Request.Length = 0; + Timeout = USB_BOOT_GENERAL_CMD_TIMEOUT / USB_MASS_1_MILLISECOND; + + Status = UsbIo->UsbControlTransfer ( + UsbIo, + &Request, + EfiUsbNoData, + Timeout, + NULL, + 0, + &CmdResult + ); + + return Status; +} |