mirror of https://github.com/acidanthera/audk.git
525 lines
16 KiB
C
525 lines
16 KiB
C
/** @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[12] = "DATA";
|
|
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 {
|
|
AsciiStrnCpy (Response + 4, NumBytesString, 8);
|
|
mTransport->Send (sizeof(Response), 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.
|
|
AsciiStrnCpy (Command, Data, Size);
|
|
Command[Size] = '\0';
|
|
|
|
// 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;
|
|
}
|