/******************************************************************************* Copyright (C) 2016 Marvell International Ltd. SPDX-License-Identifier: BSD-2-Clause-Patent *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CMD_NAME_STRING L"fupdate" #define SHELL_USE_DEVICE_PATH_PARAM L"-p" #define SHELL_DEVICE_NAME_PARAM L"DeviceName" #define SHELL_FILE_NAME_PARAM L"LocalFileName" #define SHELL_HELP_PARAM L"help" #define SHELL_LIST_PARAM L"list" #define MAIN_HDR_MAGIC 0xB105B002 STATIC EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL *EfiDevicePathFromTextProtocol; STATIC MARVELL_SPI_MASTER_PROTOCOL *SpiMasterProtocol; STATIC MARVELL_SPI_FLASH_PROTOCOL *SpiFlashProtocol; STATIC EFI_BLOCK_IO_PROTOCOL *BlkIo; STATIC CONST CHAR16 gShellFUpdateFileName[] = L"ShellCommands"; STATIC EFI_HANDLE gShellFUpdateHiiHandle = NULL; STATIC CONST SHELL_PARAM_ITEM ParamList[] = { {SHELL_HELP_PARAM, TypeFlag}, {SHELL_LIST_PARAM, TypeFlag}, {SHELL_USE_DEVICE_PATH_PARAM, TypeFlag}, {SHELL_FILE_NAME_PARAM, TypePosition}, {SHELL_DEVICE_NAME_PARAM, TypePosition}, {NULL , TypeMax} }; typedef struct { // Bytes UINT32 Magic; // 0-3 UINT32 PrologSize; // 4-7 UINT32 PrologChecksum; // 8-11 UINT32 BootImageSize; // 12-15 UINT32 BootImageChecksum; // 16-19 UINT32 Reserved0; // 20-23 UINT32 LoadAddr; // 24-27 UINT32 ExecAddr; // 28-31 UINT8 UartConfig; // 32 UINT8 Baudrate; // 33 UINT8 ExtCount; // 34 UINT8 AuxFlags; // 35 UINT32 IoArg0; // 36-39 UINT32 IoArg1; // 40-43 UINT32 IoArg2; // 43-47 UINT32 IoArg3; // 48-51 UINT32 Reserved1; // 52-55 UINT32 Reserved2; // 56-59 UINT32 Reserved3; // 60-63 } MV_FIRMWARE_IMAGE_HEADER; typedef EFI_STATUS (EFIAPI *FLASH_COMMAND) ( UINT64 FileSize, UINTN *FileBuffer, EFI_LBA Offset ); STATIC EFI_STATUS SpiFlashProbe ( IN SPI_DEVICE *Slave ) { EFI_STATUS Status; // Read SPI flash ID Status = SpiFlashProtocol->ReadId (Slave, FALSE); if (EFI_ERROR (Status)) { return SHELL_ABORTED; } Status = SpiFlashProtocol->Init (SpiFlashProtocol, Slave); if (EFI_ERROR(Status)) { Print (L"%s: Cannot initialize flash device\n", CMD_NAME_STRING); return EFI_DEVICE_ERROR; } return EFI_SUCCESS; } /** Verify if selected device is valid for the firmware update. @param[in] Handle Handle of verified device @param[in] Offset [OPTIONAL] Additional parameter required for BlkIo->WriteBlocks, filled depending on detected device type. **/ STATIC EFI_STATUS IsDeviceSupported ( IN EFI_HANDLE Handle, OUT EFI_LBA *Offset OPTIONAL ) { EFI_STATUS Status; EFI_DEVICE_PATH_PROTOCOL *DevicePath; // // Skip handles that do not have device path protocol // Status = gBS->OpenProtocol (Handle, &gEfiDevicePathProtocolGuid, (VOID**)&DevicePath, gImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } // // Skip handles that are not block devices // Status = gBS->OpenProtocol (Handle, &gEfiBlockIoProtocolGuid, NULL, NULL, NULL, EFI_OPEN_PROTOCOL_TEST_PROTOCOL); if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } while (!IsDevicePathEnd (DevicePath)) { if (DevicePath->Type == MESSAGING_DEVICE_PATH) { // // Search for SD/MMC devices. // if (DevicePath->SubType == MSG_SD_DP) { // // Only flashing in the beginning of SD card makes sense. // DevicePath = NextDevicePathNode (DevicePath); if (!IsDevicePathEnd (DevicePath)) { return EFI_UNSUPPORTED; } if (Offset != NULL) { *Offset = 1; } return EFI_SUCCESS; } if (DevicePath->SubType == MSG_EMMC_DP) { // // Filter out entire MMC device (ctrl(0x0)) // and boot partitions (ctrl(0x1)/ctrl(0x2)) as valid for // the firmware update. // DevicePath = NextDevicePathNode (DevicePath); if (IsDevicePathEnd (DevicePath)) { return EFI_UNSUPPORTED; } DevicePath = NextDevicePathNode (DevicePath); if (!IsDevicePathEnd (DevicePath)) { return EFI_UNSUPPORTED; } if (Offset != NULL) { *Offset = 0; } return EFI_SUCCESS; } } DevicePath = NextDevicePathNode (DevicePath); } return EFI_UNSUPPORTED; } /** Print information about single device supporting the firmware update. @param[in] Handle Handle of verified device **/ STATIC EFI_STATUS PrintSupportedDevice ( IN EFI_HANDLE Handle ) { CHAR16 *Name; gEfiShellProtocol->GetDeviceName (Handle, EFI_DEVICE_NAME_USE_DEVICE_PATH, NULL, &Name); if (Name != NULL) { ShellPrintEx (-1, -1, L"%H%02x%N %s\n", ConvertHandleToHandleIndex (Handle), Name); } return EFI_SUCCESS; } /** Print information about all devices supporting the firmware update. **/ STATIC EFI_STATUS ListSupportedDevices ( ) { UINTN LoopVar; EFI_HANDLE Handle; EFI_STATUS Status; ShellPrintEx (-1, -1, L"%BHandle Path%N\n"); ShellPrintEx (-1, -1, L"%Hspi%N spi\n"); for (LoopVar = 1; ; LoopVar++) { Handle = ConvertHandleIndexToHandle (LoopVar); if (Handle == NULL) { break; } Status = IsDeviceSupported (Handle, NULL); if (!EFI_ERROR (Status)) { PrintSupportedDevice (Handle); } } return EFI_SUCCESS; } STATIC EFI_STATUS CheckImageHeader ( IN OUT UINTN *ImageHeader ) { MV_FIRMWARE_IMAGE_HEADER *Header; UINT32 HeaderLength, Checksum, ChecksumBackup; Header = (MV_FIRMWARE_IMAGE_HEADER *)ImageHeader; HeaderLength = Header->PrologSize; ChecksumBackup = Header->PrologChecksum; // Compare magic number if (Header->Magic != MAIN_HDR_MAGIC) { Print (L"%s: Bad Image magic 0x%08x != 0x%08x\n", CMD_NAME_STRING, Header->Magic, MAIN_HDR_MAGIC); return EFI_DEVICE_ERROR; } // The checksum field is discarded from calculation Header->PrologChecksum = 0; Checksum = CalculateSum32 ((UINT32 *)Header, HeaderLength); if (Checksum != ChecksumBackup) { Print (L"%s: Bad Image checksum. 0x%x != 0x%x\n", CMD_NAME_STRING, Checksum, ChecksumBackup); return EFI_DEVICE_ERROR; } // Restore checksum backup Header->PrologChecksum = ChecksumBackup; return 0; } STATIC EFI_STATUS PrepareFirmwareImage ( IN LIST_ENTRY *CheckPackage, IN OUT SHELL_FILE_HANDLE *FileHandle, IN OUT UINTN **FileBuffer, IN OUT UINT64 *FileSize, IN UINT64 Alignment ) { CONST CHAR16 *FileStr; EFI_STATUS Status; UINT64 OpenMode; UINTN *Buffer; UINTN AlignedFileSize; // Parse string from commandline FileStr = ShellCommandLineGetRawValue (CheckPackage, 1); if (FileStr == NULL) { Print (L"%s: No image specified\n", CMD_NAME_STRING); return EFI_INVALID_PARAMETER; } else { Status = ShellIsFile (FileStr); if (EFI_ERROR(Status)) { Print (L"%s: File not found\n", CMD_NAME_STRING); return EFI_INVALID_PARAMETER; } } // Obtain file size OpenMode = EFI_FILE_MODE_READ; Status = ShellOpenFileByName (FileStr, FileHandle, OpenMode, 0); if (EFI_ERROR (Status)) { Print (L"%s: Cannot open Image file\n", CMD_NAME_STRING); return EFI_DEVICE_ERROR; } Status = FileHandleGetSize (*FileHandle, FileSize); if (EFI_ERROR (Status)) { Print (L"%s: Cannot get Image file size\n", CMD_NAME_STRING); } // Allocate aligned buffer in case of updating firmware in a BlockDevice if (Alignment > 0) { AlignedFileSize = *FileSize + (Alignment - (*FileSize % Alignment)); } else { AlignedFileSize = *FileSize; } // Read Image header into buffer Buffer = AllocateZeroPool (AlignedFileSize); if (Buffer == NULL) { Print (L"%s: Fail to allocate buffer\n", CMD_NAME_STRING); ShellCloseFile (FileHandle); return EFI_OUT_OF_RESOURCES; } Status = FileHandleRead (*FileHandle, (UINTN *)FileSize, Buffer); if (EFI_ERROR (Status)) { Print (L"%s: Cannot read Image file header\n", CMD_NAME_STRING); ShellCloseFile (FileHandle); FreePool (Buffer); return EFI_DEVICE_ERROR; } *FileBuffer = Buffer; *FileSize = AlignedFileSize; return EFI_SUCCESS; } /** Update firmware image in the block device. @param[in] FileSize Size of the file to be flashed @param[in] *FileBuffer Pointer to the file in memory @param[in] Offset First logical block to be updated. **/ STATIC EFI_STATUS EFIAPI ProgramBlockDevice ( IN UINT64 FileSize, IN UINTN *FileBuffer, IN EFI_LBA Offset ) { EFI_STATUS Status; Print (L"Updating image in BlockDevice\n"); Status = BlkIo->WriteBlocks (BlkIo, BlkIo->Media->MediaId, Offset, FileSize, FileBuffer); if (EFI_ERROR (Status)) { Print (L"%s: Cannot write to device (Status: %r)\n", CMD_NAME_STRING, Status); return Status; } Status = BlkIo->FlushBlocks (BlkIo); if (EFI_ERROR (Status)) { Print (L"%s: Cannot flush to device (Status: %r)\n", CMD_NAME_STRING, Status); return Status; } return EFI_SUCCESS; } /** Update firmware image in the SPI flash. @param[in] FileSize Size of the file to be flashed @param[in] *FileBuffer Pointer to the file in memory @param[in] Offset First logical block to be updated. Irrelevant for SPI. **/ STATIC EFI_STATUS EFIAPI ProgramSpiFlash ( IN UINT64 FileSize, IN UINTN *FileBuffer, IN EFI_LBA Offset ) { SPI_DEVICE *SpiFlash; EFI_STATUS Status; // Locate SPI protocols Status = gBS->LocateProtocol (&gMarvellSpiFlashProtocolGuid, NULL, (VOID **)&SpiFlashProtocol); if (EFI_ERROR (Status)) { Print (L"%s: Cannot locate SpiFlash protocol\n", CMD_NAME_STRING); return Status; } Status = gBS->LocateProtocol (&gMarvellSpiMasterProtocolGuid, NULL, (VOID **)&SpiMasterProtocol); if (EFI_ERROR (Status)) { Print (L"%s: Cannot locate SpiMaster protocol\n", CMD_NAME_STRING); return Status; } // Setup and probe SPI flash SpiFlash = NULL; SpiFlash = SpiMasterProtocol->SetupDevice (SpiMasterProtocol, SpiFlash, PcdGet32 (PcdSpiFlashCs), PcdGet32 (PcdSpiFlashMode)); if (SpiFlash == NULL) { Print(L"%s: Cannot allocate SPI device!\n", CMD_NAME_STRING); return EFI_ABORTED; } Status = SpiFlashProbe (SpiFlash); if (EFI_ERROR (Status)) { Print (L"%s: Error while performing SPI flash probe\n", CMD_NAME_STRING); goto FlashProbeError; } // Update firmware image in flash at offset 0x0 Status = SpiFlashProtocol->Update (SpiFlash, 0x0, FileSize, (UINT8 *)FileBuffer); if (EFI_ERROR (Status)) { Print (L"%s: Error while performing flash update\n", CMD_NAME_STRING); goto FlashProbeError; } // Release resources SpiMasterProtocol->FreeDevice (SpiFlash); return EFI_SUCCESS; FlashProbeError: SpiMasterProtocol->FreeDevice (SpiFlash); return Status; } /** Parse commandline parameters and pick device for the firmware update. @param[in] *CheckPackage Shell command argument list. @param[in/out] *FlashCommand Pointer to firmware update command. Picked, depending on the device type. @param[in/out] *Alignment File buffer alignment value. @param[in/out] *Offset First logical block to be updated. **/ STATIC EFI_STATUS EFIAPI SelectDevice ( IN LIST_ENTRY *CheckPackage, IN OUT FLASH_COMMAND *FlashCommand, IN OUT UINT64 *Alignment, IN OUT EFI_LBA *Offset ) { EFI_DEVICE_PATH_PROTOCOL *DevicePath; CONST CHAR16 *DeviceName; EFI_HANDLE Handle; UINTN HandleIndex; EFI_STATUS Status; // // SPI flash // DeviceName = ShellCommandLineGetRawValue (CheckPackage, 2); if (DeviceName == NULL || StrnCmp(DeviceName, L"spi", 4) == 0) { *FlashCommand = ProgramSpiFlash; *Alignment = 0; *Offset = 0; return EFI_SUCCESS; } if (ShellCommandLineGetFlag (CheckPackage, SHELL_USE_DEVICE_PATH_PARAM)) { // // SD/MMC is selected using device path string. // Status = gBS->LocateProtocol (&gEfiDevicePathFromTextProtocolGuid, NULL, (VOID **)&EfiDevicePathFromTextProtocol); if (EFI_ERROR (Status)) { Print (L"%s: cannot locate EfiDevicePathFromText protocol\n", CMD_NAME_STRING); return EFI_INVALID_PARAMETER; } DevicePath = EfiDevicePathFromTextProtocol->ConvertTextToDevicePath (DeviceName); if (DevicePath == NULL) { Print (L"%s: cannot locate EfiDevicePathFromText protocol\n", CMD_NAME_STRING); return EFI_INVALID_PARAMETER; } Status = gBS->LocateDevicePath (&gEfiBlockIoProtocolGuid, &DevicePath, &Handle); if (EFI_ERROR (Status)) { Print (L"%s: cannot locate selected block device\n", CMD_NAME_STRING); return Status; } } else { // // SD/MMC device is selected using handle number. // HandleIndex = ShellHexStrToUintn (DeviceName); if (HandleIndex == (UINTN)(-1)) { Print (L"%s: handle to device have to be hex number\n", CMD_NAME_STRING); return EFI_INVALID_PARAMETER; } Handle = ConvertHandleIndexToHandle (HandleIndex); if (Handle == NULL) { Print (L"%s: %x is not correct device handle\n", CMD_NAME_STRING, HandleIndex); return EFI_INVALID_PARAMETER; } } Status = IsDeviceSupported (Handle, Offset); if (EFI_ERROR (Status)) { Print (L"%s: device not supported\n", CMD_NAME_STRING); return EFI_INVALID_PARAMETER; } Status = gBS->OpenProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID**)&BlkIo, gImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR (Status)) { Print (L"%s: Cannot open device BlokIo protocol\n", CMD_NAME_STRING); return Status; } *FlashCommand = ProgramBlockDevice; *Alignment = BlkIo->Media->BlockSize; return EFI_SUCCESS; } /** Return the file name of the help text file if not using HII. @return The string pointer to the file name. **/ STATIC CONST CHAR16* EFIAPI ShellCommandGetManFileNameFUpdate ( VOID ) { return gShellFUpdateFileName; } STATIC VOID FUpdateUsage ( VOID ) { Print (L"\nFirmware update command\n" "fupdate [-p] [Device]\n\n" "LocalFilePath - path to local firmware image file\n" "-p - When flag is selected Device is interpreted\n" " as device path, not device handle.\n" "Device - Select device which will be flashed.\n" " Supported devices can be listed using \"fupdate list\"\n" " command. Device is represented by its handle.\n" " The default value is spi.\n" "EXAMPLES:\n" " * Update firmware in SPI flash from file fs2:flash-image.bin\n" " fupdate fs2:flash-image.bin\n" " * Update firmware in device with handle 5F from file flash-image.bin\n" " fupdate flash-image.bin 5F\n" " * Update firmware in device with selected path from file flash.bin\n" " fupdate flash.bin -p VenHw(0D51905B-B77E-452A-A2C0-ECA0CC8D514A,000078F20000000000)/SD(0x0)\n" " * List supported devices\n" " fupdate list\n" ); } STATIC SHELL_STATUS EFIAPI ShellCommandRunFUpdate ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { SHELL_FILE_HANDLE FileHandle; FLASH_COMMAND FlashCommand; LIST_ENTRY *CheckPackage; EFI_LBA Offset; UINT64 Alignment; UINT64 FileSize; UINTN *FileBuffer; CHAR16 *ProblemParam; EFI_STATUS Status; FileBuffer = NULL; // Parse command line Status = ShellInitialize (); if (EFI_ERROR (Status)) { Print (L"%s: Error while initializing Shell\n", CMD_NAME_STRING); ASSERT_EFI_ERROR (Status); return SHELL_ABORTED; } Status = ShellCommandLineParse (ParamList, &CheckPackage, &ProblemParam, TRUE); if (EFI_ERROR (Status)) { Print (L"%s: Invalid parameter\n", CMD_NAME_STRING); return SHELL_ABORTED; } if (ShellCommandLineGetFlag (CheckPackage, SHELL_HELP_PARAM)) { FUpdateUsage (); return EFI_SUCCESS; } if (ShellCommandLineGetFlag (CheckPackage, SHELL_LIST_PARAM)) { ListSupportedDevices (); return EFI_SUCCESS; } // Select device to flash Status = SelectDevice (CheckPackage, &FlashCommand, &Alignment, &Offset); if (EFI_ERROR (Status)) { return SHELL_ABORTED; } // Prepare local file to be burned into flash Status = PrepareFirmwareImage (CheckPackage, &FileHandle, &FileBuffer, &FileSize, Alignment); if (EFI_ERROR (Status)) { return SHELL_ABORTED; } // Check image checksum and magic Status = CheckImageHeader (FileBuffer); if (EFI_ERROR(Status)) { goto HeaderError; } // Update firmware image Status = FlashCommand (FileSize, FileBuffer, Offset); if (EFI_ERROR (Status)) { goto HeaderError; } FreePool (FileBuffer); ShellCloseFile (&FileHandle); Print (L"%s: Update %d bytes at offset 0x%x succeeded!\n", CMD_NAME_STRING, FileSize, Offset * Alignment); return EFI_SUCCESS; HeaderError: FreePool (FileBuffer); ShellCloseFile (&FileHandle); return SHELL_ABORTED; } EFI_STATUS EFIAPI ShellFUpdateCommandConstructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; gShellFUpdateHiiHandle = NULL; gShellFUpdateHiiHandle = HiiAddPackages ( &gShellFUpdateHiiGuid, gImageHandle, UefiShellFUpdateCommandLibStrings, NULL ); if (gShellFUpdateHiiHandle == NULL) { Print (L"%s: Cannot add Hii package\n", CMD_NAME_STRING); return EFI_DEVICE_ERROR; } Status = ShellCommandRegisterCommandName ( CMD_NAME_STRING, ShellCommandRunFUpdate, ShellCommandGetManFileNameFUpdate, 0, CMD_NAME_STRING, TRUE, gShellFUpdateHiiHandle, STRING_TOKEN (STR_GET_HELP_FUPDATE) ); if (EFI_ERROR(Status)) { Print (L"%s: Error while registering command\n", CMD_NAME_STRING); return SHELL_ABORTED; } return EFI_SUCCESS; } EFI_STATUS EFIAPI ShellFUpdateCommandDestructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { if (gShellFUpdateHiiHandle != NULL) { HiiRemovePackages (gShellFUpdateHiiHandle); } return EFI_SUCCESS; }