| /** @file | |
| Copyright (c) 2013-2014, ARM Ltd. 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 "AndroidFastbootApp.h" | |
| #include <Protocol/AndroidFastbootTransport.h> | |
| #include <Protocol/AndroidFastbootPlatform.h> | |
| #include <Protocol/SimpleTextOut.h> | |
| #include <Protocol/SimpleTextIn.h> | |
| #include <Library/PcdLib.h> | |
| #include <Library/UefiRuntimeServicesTableLib.h> | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Library/UefiApplicationEntryPoint.h> | |
| #include <Library/PrintLib.h> | |
| /* | |
| * UEFI Application using the FASTBOOT_TRANSPORT_PROTOCOL and | |
| * FASTBOOT_PLATFORM_PROTOCOL to implement the Android Fastboot protocol. | |
| */ | |
| STATIC FASTBOOT_TRANSPORT_PROTOCOL *mTransport; | |
| STATIC FASTBOOT_PLATFORM_PROTOCOL *mPlatform; | |
| STATIC EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *mTextOut; | |
| typedef enum { | |
| ExpectCmdState, | |
| ExpectDataState, | |
| FastbootStateMax | |
| } ANDROID_FASTBOOT_STATE; | |
| STATIC ANDROID_FASTBOOT_STATE mState = ExpectCmdState; | |
| // When in ExpectDataState, the number of bytes of data to expect: | |
| STATIC UINT64 mNumDataBytes; | |
| // .. and the number of bytes so far received this data phase | |
| STATIC UINT64 mBytesReceivedSoFar; | |
| // .. and the buffer to save data into | |
| STATIC UINT8 *mDataBuffer = NULL; | |
| // Event notify functions, from which gBS->Exit shouldn't be called, can signal | |
| // this event when the application should exit | |
| STATIC EFI_EVENT mFinishedEvent; | |
| STATIC EFI_EVENT mFatalSendErrorEvent; | |
| // This macro uses sizeof - only use it on arrays (i.e. string literals) | |
| #define SEND_LITERAL(Str) mTransport->Send ( \ | |
| sizeof (Str) - 1, \ | |
| Str, \ | |
| &mFatalSendErrorEvent \ | |
| ) | |
| #define MATCH_CMD_LITERAL(Cmd, Buf) !AsciiStrnCmp (Cmd, Buf, sizeof (Cmd) - 1) | |
| #define IS_LOWERCASE_ASCII(Char) (Char >= 'a' && Char <= 'z') | |
| #define FASTBOOT_STRING_MAX_LENGTH 256 | |
| #define FASTBOOT_COMMAND_MAX_LENGTH 64 | |
| STATIC | |
| VOID | |
| HandleGetVar ( | |
| IN CHAR8 *CmdArg | |
| ) | |
| { | |
| CHAR8 Response[FASTBOOT_COMMAND_MAX_LENGTH + 1] = "OKAY"; | |
| EFI_STATUS Status; | |
| // Respond to getvar:version with 0.4 (version of Fastboot protocol) | |
| if (!AsciiStrnCmp ("version", CmdArg, sizeof ("version") - 1 )) { | |
| SEND_LITERAL ("OKAY" ANDROID_FASTBOOT_VERSION); | |
| } else { | |
| // All other variables are assumed to be platform specific | |
| Status = mPlatform->GetVar (CmdArg, Response + 4); | |
| if (EFI_ERROR (Status)) { | |
| SEND_LITERAL ("FAILSomething went wrong when looking up the variable"); | |
| } else { | |
| mTransport->Send (AsciiStrLen (Response), Response, &mFatalSendErrorEvent); | |
| } | |
| } | |
| } | |
| STATIC | |
| VOID | |
| HandleDownload ( | |
| IN CHAR8 *NumBytesString | |
| ) | |
| { | |
| CHAR8 Response[13]; | |
| CHAR16 OutputString[FASTBOOT_STRING_MAX_LENGTH]; | |
| // Argument is 8-character ASCII string hex representation of number of bytes | |
| // that will be sent in the data phase. | |
| // Response is "DATA" + that same 8-character string. | |
| // Replace any previously downloaded data | |
| if (mDataBuffer != NULL) { | |
| FreePool (mDataBuffer); | |
| mDataBuffer = NULL; | |
| } | |
| // Parse out number of data bytes to expect | |
| mNumDataBytes = AsciiStrHexToUint64 (NumBytesString); | |
| if (mNumDataBytes == 0) { | |
| mTextOut->OutputString (mTextOut, L"ERROR: Fail to get the number of bytes to download.\r\n"); | |
| SEND_LITERAL ("FAILFailed to get the number of bytes to download"); | |
| return; | |
| } | |
| UnicodeSPrint (OutputString, sizeof (OutputString), L"Downloading %d bytes\r\n", mNumDataBytes); | |
| mTextOut->OutputString (mTextOut, OutputString); | |
| mDataBuffer = AllocatePool (mNumDataBytes); | |
| if (mDataBuffer == NULL) { | |
| SEND_LITERAL ("FAILNot enough memory"); | |
| } else { | |
| ZeroMem (Response, sizeof Response); | |
| AsciiSPrint (Response, sizeof Response, "DATA%x", | |
| (UINT32)mNumDataBytes); | |
| mTransport->Send (sizeof Response - 1, Response, &mFatalSendErrorEvent); | |
| mState = ExpectDataState; | |
| mBytesReceivedSoFar = 0; | |
| } | |
| } | |
| STATIC | |
| VOID | |
| HandleFlash ( | |
| IN CHAR8 *PartitionName | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| CHAR16 OutputString[FASTBOOT_STRING_MAX_LENGTH]; | |
| // Build output string | |
| UnicodeSPrint (OutputString, sizeof (OutputString), L"Flashing partition %a\r\n", PartitionName); | |
| mTextOut->OutputString (mTextOut, OutputString); | |
| if (mDataBuffer == NULL) { | |
| // Doesn't look like we were sent any data | |
| SEND_LITERAL ("FAILNo data to flash"); | |
| return; | |
| } | |
| Status = mPlatform->FlashPartition ( | |
| PartitionName, | |
| mNumDataBytes, | |
| mDataBuffer | |
| ); | |
| if (Status == EFI_NOT_FOUND) { | |
| SEND_LITERAL ("FAILNo such partition."); | |
| mTextOut->OutputString (mTextOut, L"No such partition.\r\n"); | |
| } else if (EFI_ERROR (Status)) { | |
| SEND_LITERAL ("FAILError flashing partition."); | |
| mTextOut->OutputString (mTextOut, L"Error flashing partition.\r\n"); | |
| DEBUG ((EFI_D_ERROR, "Couldn't flash image: %r\n", Status)); | |
| } else { | |
| mTextOut->OutputString (mTextOut, L"Done.\r\n"); | |
| SEND_LITERAL ("OKAY"); | |
| } | |
| } | |
| STATIC | |
| VOID | |
| HandleErase ( | |
| IN CHAR8 *PartitionName | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| CHAR16 OutputString[FASTBOOT_STRING_MAX_LENGTH]; | |
| // Build output string | |
| UnicodeSPrint (OutputString, sizeof (OutputString), L"Erasing partition %a\r\n", PartitionName); | |
| mTextOut->OutputString (mTextOut, OutputString); | |
| Status = mPlatform->ErasePartition (PartitionName); | |
| if (EFI_ERROR (Status)) { | |
| SEND_LITERAL ("FAILCheck device console."); | |
| DEBUG ((EFI_D_ERROR, "Couldn't erase image: %r\n", Status)); | |
| } else { | |
| SEND_LITERAL ("OKAY"); | |
| } | |
| } | |
| STATIC | |
| VOID | |
| HandleBoot ( | |
| VOID | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| mTextOut->OutputString (mTextOut, L"Booting downloaded image\r\n"); | |
| if (mDataBuffer == NULL) { | |
| // Doesn't look like we were sent any data | |
| SEND_LITERAL ("FAILNo image in memory"); | |
| return; | |
| } | |
| // We don't really have any choice but to report success, because once we | |
| // boot we lose control of the system. | |
| SEND_LITERAL ("OKAY"); | |
| Status = BootAndroidBootImg (mNumDataBytes, mDataBuffer); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "Failed to boot downloaded image: %r\n", Status)); | |
| } | |
| // We shouldn't get here | |
| } | |
| STATIC | |
| VOID | |
| HandleOemCommand ( | |
| IN CHAR8 *Command | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| Status = mPlatform->DoOemCommand (Command); | |
| if (Status == EFI_NOT_FOUND) { | |
| SEND_LITERAL ("FAILOEM Command not recognised."); | |
| } else if (Status == EFI_DEVICE_ERROR) { | |
| SEND_LITERAL ("FAILError while executing command"); | |
| } else if (EFI_ERROR (Status)) { | |
| SEND_LITERAL ("FAIL"); | |
| } else { | |
| SEND_LITERAL ("OKAY"); | |
| } | |
| } | |
| STATIC | |
| VOID | |
| AcceptCmd ( | |
| IN UINTN Size, | |
| IN CONST CHAR8 *Data | |
| ) | |
| { | |
| CHAR8 Command[FASTBOOT_COMMAND_MAX_LENGTH + 1]; | |
| // Max command size is 64 bytes | |
| if (Size > FASTBOOT_COMMAND_MAX_LENGTH) { | |
| SEND_LITERAL ("FAILCommand too large"); | |
| return; | |
| } | |
| // Commands aren't null-terminated. Let's get a null-terminated version. | |
| AsciiStrnCpyS (Command, sizeof Command, Data, Size); | |
| // Parse command | |
| if (MATCH_CMD_LITERAL ("getvar", Command)) { | |
| HandleGetVar (Command + sizeof ("getvar")); | |
| } else if (MATCH_CMD_LITERAL ("download", Command)) { | |
| HandleDownload (Command + sizeof ("download")); | |
| } else if (MATCH_CMD_LITERAL ("verify", Command)) { | |
| SEND_LITERAL ("FAILNot supported"); | |
| } else if (MATCH_CMD_LITERAL ("flash", Command)) { | |
| HandleFlash (Command + sizeof ("flash")); | |
| } else if (MATCH_CMD_LITERAL ("erase", Command)) { | |
| HandleErase (Command + sizeof ("erase")); | |
| } else if (MATCH_CMD_LITERAL ("boot", Command)) { | |
| HandleBoot (); | |
| } else if (MATCH_CMD_LITERAL ("continue", Command)) { | |
| SEND_LITERAL ("OKAY"); | |
| mTextOut->OutputString (mTextOut, L"Received 'continue' command. Exiting Fastboot mode\r\n"); | |
| gBS->SignalEvent (mFinishedEvent); | |
| } else if (MATCH_CMD_LITERAL ("reboot", Command)) { | |
| if (MATCH_CMD_LITERAL ("reboot-booloader", Command)) { | |
| // fastboot_protocol.txt: | |
| // "reboot-bootloader Reboot back into the bootloader." | |
| // I guess this means reboot back into fastboot mode to save the user | |
| // having to do whatever they did to get here again. | |
| // Here we just reboot normally. | |
| SEND_LITERAL ("INFOreboot-bootloader not supported, rebooting normally."); | |
| } | |
| SEND_LITERAL ("OKAY"); | |
| gRT->ResetSystem (EfiResetCold, EFI_SUCCESS, 0, NULL); | |
| // Shouldn't get here | |
| DEBUG ((EFI_D_ERROR, "Fastboot: gRT->ResetSystem didn't work\n")); | |
| } else if (MATCH_CMD_LITERAL ("powerdown", Command)) { | |
| SEND_LITERAL ("OKAY"); | |
| gRT->ResetSystem (EfiResetShutdown, EFI_SUCCESS, 0, NULL); | |
| // Shouldn't get here | |
| DEBUG ((EFI_D_ERROR, "Fastboot: gRT->ResetSystem didn't work\n")); | |
| } else if (MATCH_CMD_LITERAL ("oem", Command)) { | |
| // The "oem" command isn't in the specification, but it was observed in the | |
| // wild, followed by a space, followed by the actual command. | |
| HandleOemCommand (Command + sizeof ("oem")); | |
| } else if (IS_LOWERCASE_ASCII (Command[0])) { | |
| // Commands starting with lowercase ASCII characters are reserved for the | |
| // Fastboot protocol. If we don't recognise it, it's probably the future | |
| // and there are new commmands in the protocol. | |
| // (By the way, the "oem" command mentioned above makes this reservation | |
| // redundant, but we handle it here to be spec-compliant) | |
| SEND_LITERAL ("FAILCommand not recognised. Check Fastboot version."); | |
| } else { | |
| HandleOemCommand (Command); | |
| } | |
| } | |
| STATIC | |
| VOID | |
| AcceptData ( | |
| IN UINTN Size, | |
| IN VOID *Data | |
| ) | |
| { | |
| UINT32 RemainingBytes = mNumDataBytes - mBytesReceivedSoFar; | |
| CHAR16 OutputString[FASTBOOT_STRING_MAX_LENGTH]; | |
| STATIC UINTN Count = 0; | |
| // Protocol doesn't say anything about sending extra data so just ignore it. | |
| if (Size > RemainingBytes) { | |
| Size = RemainingBytes; | |
| } | |
| CopyMem (&mDataBuffer[mBytesReceivedSoFar], Data, Size); | |
| mBytesReceivedSoFar += Size; | |
| // Show download progress. Don't do it for every packet as outputting text | |
| // might be time consuming - do it on the last packet and on every 32nd packet | |
| if ((Count++ % 32) == 0 || Size == RemainingBytes) { | |
| // (Note no newline in format string - it will overwrite the line each time) | |
| UnicodeSPrint ( | |
| OutputString, | |
| sizeof (OutputString), | |
| L"\r%8d / %8d bytes downloaded (%d%%)", | |
| mBytesReceivedSoFar, | |
| mNumDataBytes, | |
| (mBytesReceivedSoFar * 100) / mNumDataBytes // percentage | |
| ); | |
| mTextOut->OutputString (mTextOut, OutputString); | |
| } | |
| if (mBytesReceivedSoFar == mNumDataBytes) { | |
| // Download finished. | |
| mTextOut->OutputString (mTextOut, L"\r\n"); | |
| SEND_LITERAL ("OKAY"); | |
| mState = ExpectCmdState; | |
| } | |
| } | |
| /* | |
| This is the NotifyFunction passed to CreateEvent in the FastbootAppEntryPoint | |
| It will be called by the UEFI event framework when the transport protocol | |
| implementation signals that data has been received from the Fastboot host. | |
| The parameters are ignored. | |
| */ | |
| STATIC | |
| VOID | |
| DataReady ( | |
| IN EFI_EVENT Event, | |
| IN VOID *Context | |
| ) | |
| { | |
| UINTN Size; | |
| VOID *Data; | |
| EFI_STATUS Status; | |
| do { | |
| Status = mTransport->Receive (&Size, &Data); | |
| if (!EFI_ERROR (Status)) { | |
| if (mState == ExpectCmdState) { | |
| AcceptCmd (Size, (CHAR8 *) Data); | |
| } else if (mState == ExpectDataState) { | |
| AcceptData (Size, Data); | |
| } else { | |
| ASSERT (FALSE); | |
| } | |
| FreePool (Data); | |
| } | |
| } while (!EFI_ERROR (Status)); | |
| // Quit if there was a fatal error | |
| if (Status != EFI_NOT_READY) { | |
| ASSERT (Status == EFI_DEVICE_ERROR); | |
| // (Put a newline at the beginning as we are probably in the data phase, | |
| // so the download progress line, with no '\n' is probably on the console) | |
| mTextOut->OutputString (mTextOut, L"\r\nFatal error receiving data. Exiting.\r\n"); | |
| gBS->SignalEvent (mFinishedEvent); | |
| } | |
| } | |
| /* | |
| Event notify for a fatal error in transmission. | |
| */ | |
| STATIC | |
| VOID | |
| FatalErrorNotify ( | |
| IN EFI_EVENT Event, | |
| IN VOID *Context | |
| ) | |
| { | |
| mTextOut->OutputString (mTextOut, L"Fatal error sending command response. Exiting.\r\n"); | |
| gBS->SignalEvent (mFinishedEvent); | |
| } | |
| EFI_STATUS | |
| EFIAPI | |
| FastbootAppEntryPoint ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_EVENT ReceiveEvent; | |
| EFI_EVENT WaitEventArray[2]; | |
| UINTN EventIndex; | |
| EFI_SIMPLE_TEXT_INPUT_PROTOCOL *TextIn; | |
| mDataBuffer = NULL; | |
| Status = gBS->LocateProtocol ( | |
| &gAndroidFastbootTransportProtocolGuid, | |
| NULL, | |
| (VOID **) &mTransport | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't open Fastboot Transport Protocol: %r\n", Status)); | |
| return Status; | |
| } | |
| Status = gBS->LocateProtocol (&gAndroidFastbootPlatformProtocolGuid, NULL, (VOID **) &mPlatform); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't open Fastboot Platform Protocol: %r\n", Status)); | |
| return Status; | |
| } | |
| Status = mPlatform->Init (); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't initialise Fastboot Platform Protocol: %r\n", Status)); | |
| return Status; | |
| } | |
| Status = gBS->LocateProtocol (&gEfiSimpleTextOutProtocolGuid, NULL, (VOID **) &mTextOut); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, | |
| "Fastboot: Couldn't open Text Output Protocol: %r\n", Status | |
| )); | |
| return Status; | |
| } | |
| Status = gBS->LocateProtocol (&gEfiSimpleTextInProtocolGuid, NULL, (VOID **) &TextIn); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't open Text Input Protocol: %r\n", Status)); | |
| return Status; | |
| } | |
| // Disable watchdog | |
| Status = gBS->SetWatchdogTimer (0, 0x10000, 0, NULL); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't disable watchdog timer: %r\n", Status)); | |
| } | |
| // Create event for receipt of data from the host | |
| Status = gBS->CreateEvent ( | |
| EVT_NOTIFY_SIGNAL, | |
| TPL_CALLBACK, | |
| DataReady, | |
| NULL, | |
| &ReceiveEvent | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| // Create event for exiting application when "continue" command is received | |
| Status = gBS->CreateEvent (0, TPL_CALLBACK, NULL, NULL, &mFinishedEvent); | |
| ASSERT_EFI_ERROR (Status); | |
| // Create event to pass to FASTBOOT_TRANSPORT_PROTOCOL.Send, signalling a | |
| // fatal error | |
| Status = gBS->CreateEvent ( | |
| EVT_NOTIFY_SIGNAL, | |
| TPL_CALLBACK, | |
| FatalErrorNotify, | |
| NULL, | |
| &mFatalSendErrorEvent | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| // Start listening for data | |
| Status = mTransport->Start ( | |
| ReceiveEvent | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't start transport: %r\n", Status)); | |
| return Status; | |
| } | |
| // Talk to the user | |
| mTextOut->OutputString (mTextOut, | |
| L"Android Fastboot mode - version " ANDROID_FASTBOOT_VERSION ". Press any key to quit.\r\n"); | |
| // Quit when the user presses any key, or mFinishedEvent is signalled | |
| WaitEventArray[0] = mFinishedEvent; | |
| WaitEventArray[1] = TextIn->WaitForKey; | |
| gBS->WaitForEvent (2, WaitEventArray, &EventIndex); | |
| mTransport->Stop (); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "Warning: Fastboot Transport Stop: %r\n", Status)); | |
| } | |
| mPlatform->UnInit (); | |
| return EFI_SUCCESS; | |
| } |