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