1 /** @file
2 
3   Copyright (c) 2013-2014, ARM Ltd. All rights reserved.<BR>
4 
5   SPDX-License-Identifier: BSD-2-Clause-Patent
6 
7 **/
8 
9 #include "AndroidFastbootApp.h"
10 
11 #include <Protocol/AndroidFastbootTransport.h>
12 #include <Protocol/AndroidFastbootPlatform.h>
13 #include <Protocol/SimpleTextOut.h>
14 #include <Protocol/SimpleTextIn.h>
15 
16 #include <Library/PcdLib.h>
17 #include <Library/UefiRuntimeServicesTableLib.h>
18 #include <Library/BaseMemoryLib.h>
19 #include <Library/UefiBootServicesTableLib.h>
20 #include <Library/UefiApplicationEntryPoint.h>
21 #include <Library/PrintLib.h>
22 
23 /*
24  * UEFI Application using the FASTBOOT_TRANSPORT_PROTOCOL and
25  * FASTBOOT_PLATFORM_PROTOCOL to implement the Android Fastboot protocol.
26  */
27 
28 STATIC FASTBOOT_TRANSPORT_PROTOCOL *mTransport;
29 STATIC FASTBOOT_PLATFORM_PROTOCOL  *mPlatform;
30 
31 STATIC EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *mTextOut;
32 
33 typedef enum {
34   ExpectCmdState,
35   ExpectDataState,
36   FastbootStateMax
37 } ANDROID_FASTBOOT_STATE;
38 
39 STATIC ANDROID_FASTBOOT_STATE mState = ExpectCmdState;
40 
41 // When in ExpectDataState, the number of bytes of data to expect:
42 STATIC UINT64 mNumDataBytes;
43 // .. and the number of bytes so far received this data phase
44 STATIC UINT64 mBytesReceivedSoFar;
45 // .. and the buffer to save data into
46 STATIC UINT8 *mDataBuffer = NULL;
47 
48 // Event notify functions, from which gBS->Exit shouldn't be called, can signal
49 // this event when the application should exit
50 STATIC EFI_EVENT mFinishedEvent;
51 
52 STATIC EFI_EVENT mFatalSendErrorEvent;
53 
54 // This macro uses sizeof - only use it on arrays (i.e. string literals)
55 #define SEND_LITERAL(Str) mTransport->Send (                  \
56                                         sizeof (Str) - 1,     \
57                                         Str,                  \
58                                         &mFatalSendErrorEvent \
59                                         )
60 #define MATCH_CMD_LITERAL(Cmd, Buf) !AsciiStrnCmp (Cmd, Buf, sizeof (Cmd) - 1)
61 
62 #define IS_LOWERCASE_ASCII(Char) (Char >= 'a' && Char <= 'z')
63 
64 #define FASTBOOT_STRING_MAX_LENGTH  256
65 #define FASTBOOT_COMMAND_MAX_LENGTH 64
66 
67 STATIC
68 VOID
HandleGetVar(IN CHAR8 * CmdArg)69 HandleGetVar (
70   IN CHAR8 *CmdArg
71   )
72 {
73   CHAR8      Response[FASTBOOT_COMMAND_MAX_LENGTH + 1] = "OKAY";
74   EFI_STATUS Status;
75 
76   // Respond to getvar:version with 0.4 (version of Fastboot protocol)
77   if (!AsciiStrnCmp ("version", CmdArg, sizeof ("version") - 1 )) {
78     SEND_LITERAL ("OKAY" ANDROID_FASTBOOT_VERSION);
79   } else {
80     // All other variables are assumed to be platform specific
81     Status = mPlatform->GetVar (CmdArg, Response + 4);
82     if (EFI_ERROR (Status)) {
83       SEND_LITERAL ("FAILSomething went wrong when looking up the variable");
84     } else {
85       mTransport->Send (AsciiStrLen (Response), Response, &mFatalSendErrorEvent);
86     }
87   }
88 }
89 
90 STATIC
91 VOID
HandleDownload(IN CHAR8 * NumBytesString)92 HandleDownload (
93   IN CHAR8 *NumBytesString
94   )
95 {
96   CHAR8       Response[13];
97   CHAR16      OutputString[FASTBOOT_STRING_MAX_LENGTH];
98 
99   // Argument is 8-character ASCII string hex representation of number of bytes
100   // that will be sent in the data phase.
101   // Response is "DATA" + that same 8-character string.
102 
103   // Replace any previously downloaded data
104   if (mDataBuffer != NULL) {
105     FreePool (mDataBuffer);
106     mDataBuffer = NULL;
107   }
108 
109   // Parse out number of data bytes to expect
110   mNumDataBytes = AsciiStrHexToUint64 (NumBytesString);
111   if (mNumDataBytes == 0) {
112     mTextOut->OutputString (mTextOut, L"ERROR: Fail to get the number of bytes to download.\r\n");
113     SEND_LITERAL ("FAILFailed to get the number of bytes to download");
114     return;
115   }
116 
117   UnicodeSPrint (OutputString, sizeof (OutputString), L"Downloading %d bytes\r\n", mNumDataBytes);
118   mTextOut->OutputString (mTextOut, OutputString);
119 
120   mDataBuffer = AllocatePool (mNumDataBytes);
121   if (mDataBuffer == NULL) {
122     SEND_LITERAL ("FAILNot enough memory");
123   } else {
124     ZeroMem (Response, sizeof Response);
125     AsciiSPrint (Response, sizeof Response, "DATA%x",
126       (UINT32)mNumDataBytes);
127     mTransport->Send (sizeof Response - 1, Response, &mFatalSendErrorEvent);
128 
129     mState = ExpectDataState;
130     mBytesReceivedSoFar = 0;
131   }
132 }
133 
134 STATIC
135 VOID
HandleFlash(IN CHAR8 * PartitionName)136 HandleFlash (
137   IN CHAR8 *PartitionName
138   )
139 {
140   EFI_STATUS  Status;
141   CHAR16      OutputString[FASTBOOT_STRING_MAX_LENGTH];
142 
143   // Build output string
144   UnicodeSPrint (OutputString, sizeof (OutputString), L"Flashing partition %a\r\n", PartitionName);
145   mTextOut->OutputString (mTextOut, OutputString);
146 
147   if (mDataBuffer == NULL) {
148     // Doesn't look like we were sent any data
149     SEND_LITERAL ("FAILNo data to flash");
150     return;
151   }
152 
153   Status = mPlatform->FlashPartition (
154                         PartitionName,
155                         mNumDataBytes,
156                         mDataBuffer
157                         );
158   if (Status == EFI_NOT_FOUND) {
159     SEND_LITERAL ("FAILNo such partition.");
160     mTextOut->OutputString (mTextOut, L"No such partition.\r\n");
161   } else if (EFI_ERROR (Status)) {
162     SEND_LITERAL ("FAILError flashing partition.");
163     mTextOut->OutputString (mTextOut, L"Error flashing partition.\r\n");
164     DEBUG ((EFI_D_ERROR, "Couldn't flash image:  %r\n", Status));
165   } else {
166     mTextOut->OutputString (mTextOut, L"Done.\r\n");
167     SEND_LITERAL ("OKAY");
168   }
169 }
170 
171 STATIC
172 VOID
HandleErase(IN CHAR8 * PartitionName)173 HandleErase (
174   IN CHAR8 *PartitionName
175   )
176 {
177   EFI_STATUS  Status;
178   CHAR16      OutputString[FASTBOOT_STRING_MAX_LENGTH];
179 
180   // Build output string
181   UnicodeSPrint (OutputString, sizeof (OutputString), L"Erasing partition %a\r\n", PartitionName);
182   mTextOut->OutputString (mTextOut, OutputString);
183 
184   Status = mPlatform->ErasePartition (PartitionName);
185   if (EFI_ERROR (Status)) {
186     SEND_LITERAL ("FAILCheck device console.");
187     DEBUG ((EFI_D_ERROR, "Couldn't erase image:  %r\n", Status));
188   } else {
189     SEND_LITERAL ("OKAY");
190   }
191 }
192 
193 STATIC
194 VOID
HandleBoot(VOID)195 HandleBoot (
196   VOID
197   )
198 {
199   EFI_STATUS Status;
200 
201   mTextOut->OutputString (mTextOut, L"Booting downloaded image\r\n");
202 
203   if (mDataBuffer == NULL) {
204     // Doesn't look like we were sent any data
205     SEND_LITERAL ("FAILNo image in memory");
206     return;
207   }
208 
209   // We don't really have any choice but to report success, because once we
210   // boot we lose control of the system.
211   SEND_LITERAL ("OKAY");
212 
213   Status = BootAndroidBootImg (mNumDataBytes, mDataBuffer);
214   if (EFI_ERROR (Status)) {
215     DEBUG ((EFI_D_ERROR, "Failed to boot downloaded image: %r\n", Status));
216   }
217   // We shouldn't get here
218 }
219 
220 STATIC
221 VOID
HandleOemCommand(IN CHAR8 * Command)222 HandleOemCommand (
223   IN CHAR8 *Command
224   )
225 {
226   EFI_STATUS  Status;
227 
228   Status = mPlatform->DoOemCommand (Command);
229   if (Status == EFI_NOT_FOUND) {
230     SEND_LITERAL ("FAILOEM Command not recognised.");
231   } else if (Status == EFI_DEVICE_ERROR) {
232     SEND_LITERAL ("FAILError while executing command");
233   } else if (EFI_ERROR (Status)) {
234     SEND_LITERAL ("FAIL");
235   } else {
236     SEND_LITERAL ("OKAY");
237   }
238 }
239 
240 STATIC
241 VOID
AcceptCmd(IN UINTN Size,IN CONST CHAR8 * Data)242 AcceptCmd (
243   IN        UINTN  Size,
244   IN  CONST CHAR8 *Data
245   )
246 {
247   CHAR8       Command[FASTBOOT_COMMAND_MAX_LENGTH + 1];
248 
249   // Max command size is 64 bytes
250   if (Size > FASTBOOT_COMMAND_MAX_LENGTH) {
251     SEND_LITERAL ("FAILCommand too large");
252     return;
253   }
254 
255   // Commands aren't null-terminated. Let's get a null-terminated version.
256   AsciiStrnCpyS (Command, sizeof Command, Data, Size);
257 
258   // Parse command
259   if (MATCH_CMD_LITERAL ("getvar", Command)) {
260     HandleGetVar (Command + sizeof ("getvar"));
261   } else if (MATCH_CMD_LITERAL ("download", Command)) {
262     HandleDownload (Command + sizeof ("download"));
263   } else if (MATCH_CMD_LITERAL ("verify", Command)) {
264     SEND_LITERAL ("FAILNot supported");
265   } else if (MATCH_CMD_LITERAL ("flash", Command)) {
266     HandleFlash (Command + sizeof ("flash"));
267   } else if (MATCH_CMD_LITERAL ("erase", Command)) {
268     HandleErase (Command + sizeof ("erase"));
269   } else if (MATCH_CMD_LITERAL ("boot", Command)) {
270     HandleBoot ();
271   } else if (MATCH_CMD_LITERAL ("continue", Command)) {
272     SEND_LITERAL ("OKAY");
273     mTextOut->OutputString (mTextOut, L"Received 'continue' command. Exiting Fastboot mode\r\n");
274 
275     gBS->SignalEvent (mFinishedEvent);
276   } else if (MATCH_CMD_LITERAL ("reboot", Command)) {
277     if (MATCH_CMD_LITERAL ("reboot-booloader", Command)) {
278       // fastboot_protocol.txt:
279       //    "reboot-bootloader    Reboot back into the bootloader."
280       // I guess this means reboot back into fastboot mode to save the user
281       // having to do whatever they did to get here again.
282       // Here we just reboot normally.
283       SEND_LITERAL ("INFOreboot-bootloader not supported, rebooting normally.");
284     }
285     SEND_LITERAL ("OKAY");
286     gRT->ResetSystem (EfiResetCold, EFI_SUCCESS, 0, NULL);
287 
288     // Shouldn't get here
289     DEBUG ((EFI_D_ERROR, "Fastboot: gRT->ResetSystem didn't work\n"));
290   } else if (MATCH_CMD_LITERAL ("powerdown", Command)) {
291     SEND_LITERAL ("OKAY");
292     gRT->ResetSystem (EfiResetShutdown, EFI_SUCCESS, 0, NULL);
293 
294     // Shouldn't get here
295     DEBUG ((EFI_D_ERROR, "Fastboot: gRT->ResetSystem didn't work\n"));
296   } else if (MATCH_CMD_LITERAL ("oem", Command)) {
297     // The "oem" command isn't in the specification, but it was observed in the
298     // wild, followed by a space, followed by the actual command.
299     HandleOemCommand (Command + sizeof ("oem"));
300   } else if (IS_LOWERCASE_ASCII (Command[0])) {
301     // Commands starting with lowercase ASCII characters are reserved for the
302     // Fastboot protocol. If we don't recognise it, it's probably the future
303     // and there are new commands in the protocol.
304     // (By the way, the "oem" command mentioned above makes this reservation
305     //  redundant, but we handle it here to be spec-compliant)
306     SEND_LITERAL ("FAILCommand not recognised. Check Fastboot version.");
307   } else {
308     HandleOemCommand (Command);
309   }
310 }
311 
312 STATIC
313 VOID
AcceptData(IN UINTN Size,IN VOID * Data)314 AcceptData (
315   IN  UINTN  Size,
316   IN  VOID  *Data
317   )
318 {
319   UINT32 RemainingBytes = mNumDataBytes - mBytesReceivedSoFar;
320   CHAR16 OutputString[FASTBOOT_STRING_MAX_LENGTH];
321   STATIC UINTN Count = 0;
322 
323   // Protocol doesn't say anything about sending extra data so just ignore it.
324   if (Size > RemainingBytes) {
325     Size = RemainingBytes;
326   }
327 
328   CopyMem (&mDataBuffer[mBytesReceivedSoFar], Data, Size);
329 
330   mBytesReceivedSoFar += Size;
331 
332   // Show download progress. Don't do it for every packet  as outputting text
333   // might be time consuming - do it on the last packet and on every 32nd packet
334   if ((Count++ % 32) == 0 || Size == RemainingBytes) {
335     // (Note no newline in format string - it will overwrite the line each time)
336     UnicodeSPrint (
337       OutputString,
338       sizeof (OutputString),
339       L"\r%8d / %8d bytes downloaded (%d%%)",
340       mBytesReceivedSoFar,
341       mNumDataBytes,
342       (mBytesReceivedSoFar * 100) / mNumDataBytes // percentage
343       );
344     mTextOut->OutputString (mTextOut, OutputString);
345   }
346 
347   if (mBytesReceivedSoFar == mNumDataBytes) {
348     // Download finished.
349 
350     mTextOut->OutputString (mTextOut, L"\r\n");
351     SEND_LITERAL ("OKAY");
352     mState = ExpectCmdState;
353   }
354 }
355 
356 /*
357   This is the NotifyFunction passed to CreateEvent in the FastbootAppEntryPoint
358   It will be called by the UEFI event framework when the transport protocol
359   implementation signals that data has been received from the Fastboot host.
360   The parameters are ignored.
361 */
362 STATIC
363 VOID
DataReady(IN EFI_EVENT Event,IN VOID * Context)364 DataReady (
365   IN EFI_EVENT  Event,
366   IN VOID      *Context
367   )
368 {
369   UINTN       Size;
370   VOID       *Data;
371   EFI_STATUS  Status;
372 
373   do {
374     Status = mTransport->Receive (&Size, &Data);
375     if (!EFI_ERROR (Status)) {
376       if (mState == ExpectCmdState) {
377         AcceptCmd (Size, (CHAR8 *) Data);
378       } else if (mState == ExpectDataState) {
379         AcceptData (Size, Data);
380       } else {
381         ASSERT (FALSE);
382       }
383       FreePool (Data);
384     }
385   } while (!EFI_ERROR (Status));
386 
387   // Quit if there was a fatal error
388   if (Status != EFI_NOT_READY) {
389     ASSERT (Status == EFI_DEVICE_ERROR);
390     // (Put a newline at the beginning as we are probably in the data phase,
391     //  so the download progress line, with no '\n' is probably on the console)
392     mTextOut->OutputString (mTextOut, L"\r\nFatal error receiving data. Exiting.\r\n");
393     gBS->SignalEvent (mFinishedEvent);
394   }
395 }
396 
397 /*
398   Event notify for a fatal error in transmission.
399 */
400 STATIC
401 VOID
FatalErrorNotify(IN EFI_EVENT Event,IN VOID * Context)402 FatalErrorNotify (
403   IN EFI_EVENT  Event,
404   IN VOID      *Context
405   )
406 {
407   mTextOut->OutputString (mTextOut, L"Fatal error sending command response. Exiting.\r\n");
408   gBS->SignalEvent (mFinishedEvent);
409 }
410 
411 EFI_STATUS
412 EFIAPI
FastbootAppEntryPoint(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)413 FastbootAppEntryPoint (
414   IN EFI_HANDLE                            ImageHandle,
415   IN EFI_SYSTEM_TABLE                      *SystemTable
416   )
417 {
418   EFI_STATUS                      Status;
419   EFI_EVENT                       ReceiveEvent;
420   EFI_EVENT                       WaitEventArray[2];
421   UINTN                           EventIndex;
422   EFI_SIMPLE_TEXT_INPUT_PROTOCOL *TextIn;
423   EFI_INPUT_KEY                   Key;
424 
425   mDataBuffer = NULL;
426 
427   Status = gBS->LocateProtocol (
428     &gAndroidFastbootTransportProtocolGuid,
429     NULL,
430     (VOID **) &mTransport
431     );
432   if (EFI_ERROR (Status)) {
433     DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't open Fastboot Transport Protocol: %r\n", Status));
434     return Status;
435   }
436 
437   Status = gBS->LocateProtocol (&gAndroidFastbootPlatformProtocolGuid, NULL, (VOID **) &mPlatform);
438   if (EFI_ERROR (Status)) {
439     DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't open Fastboot Platform Protocol: %r\n", Status));
440     return Status;
441   }
442 
443   Status = mPlatform->Init ();
444   if (EFI_ERROR (Status)) {
445     DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't initialise Fastboot Platform Protocol: %r\n", Status));
446     return Status;
447   }
448 
449   Status = gBS->LocateProtocol (&gEfiSimpleTextOutProtocolGuid, NULL, (VOID **) &mTextOut);
450   if (EFI_ERROR (Status)) {
451     DEBUG ((EFI_D_ERROR,
452       "Fastboot: Couldn't open Text Output Protocol: %r\n", Status
453       ));
454     return Status;
455   }
456 
457   Status = gBS->LocateProtocol (&gEfiSimpleTextInProtocolGuid, NULL, (VOID **) &TextIn);
458   if (EFI_ERROR (Status)) {
459     DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't open Text Input Protocol: %r\n", Status));
460     return Status;
461   }
462 
463   // Disable watchdog
464   Status = gBS->SetWatchdogTimer (0, 0x10000, 0, NULL);
465   if (EFI_ERROR (Status)) {
466     DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't disable watchdog timer: %r\n", Status));
467   }
468 
469   // Create event for receipt of data from the host
470   Status = gBS->CreateEvent (
471                   EVT_NOTIFY_SIGNAL,
472                   TPL_CALLBACK,
473                   DataReady,
474                   NULL,
475                   &ReceiveEvent
476                   );
477   ASSERT_EFI_ERROR (Status);
478 
479   // Create event for exiting application when "continue" command is received
480   Status = gBS->CreateEvent (0, TPL_CALLBACK, NULL, NULL, &mFinishedEvent);
481   ASSERT_EFI_ERROR (Status);
482 
483   // Create event to pass to FASTBOOT_TRANSPORT_PROTOCOL.Send, signalling a
484   // fatal error
485   Status = gBS->CreateEvent (
486                  EVT_NOTIFY_SIGNAL,
487                  TPL_CALLBACK,
488                  FatalErrorNotify,
489                  NULL,
490                  &mFatalSendErrorEvent
491                  );
492   ASSERT_EFI_ERROR (Status);
493 
494 
495   // Start listening for data
496   Status = mTransport->Start (
497     ReceiveEvent
498     );
499   if (EFI_ERROR (Status)) {
500     DEBUG ((EFI_D_ERROR, "Fastboot: Couldn't start transport: %r\n", Status));
501     return Status;
502   }
503 
504   // Talk to the user
505   mTextOut->OutputString (mTextOut,
506       L"Android Fastboot mode - version " ANDROID_FASTBOOT_VERSION ". Press RETURN or SPACE key to quit.\r\n");
507 
508   // Quit when the user presses any key, or mFinishedEvent is signalled
509   WaitEventArray[0] = mFinishedEvent;
510   WaitEventArray[1] = TextIn->WaitForKey;
511   while (1) {
512     gBS->WaitForEvent (2, WaitEventArray, &EventIndex);
513     Status = TextIn->ReadKeyStroke (gST->ConIn, &Key);
514     if (Key.ScanCode == SCAN_NULL) {
515       if ((Key.UnicodeChar == CHAR_CARRIAGE_RETURN) ||
516           (Key.UnicodeChar == L' ')) {
517         break;
518       }
519     }
520   }
521 
522   mTransport->Stop ();
523   if (EFI_ERROR (Status)) {
524     DEBUG ((EFI_D_ERROR, "Warning: Fastboot Transport Stop: %r\n", Status));
525   }
526   mPlatform->UnInit ();
527 
528   return EFI_SUCCESS;
529 }
530