| /** @file | |
| Basic command line parser for EBL (Embedded Boot Loader) | |
| Copyright (c) 2007, Intel Corporation<BR> | |
| Portions copyright (c) 2008-2009, Apple Inc. All rights reserved. | |
| All rights reserved. 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 "Ebl.h" | |
| // Globals for command history processing | |
| INTN mCmdHistoryEnd = -1; | |
| INTN mCmdHistoryStart = -1; | |
| INTN mCmdHistoryCurrent = -1; | |
| CHAR8 mCmdHistory[MAX_CMD_HISTORY][MAX_CMD_LINE]; | |
| CHAR8 *mCmdBlank = ""; | |
| // Globals to remember current screen geometry | |
| UINTN gScreenColumns; | |
| UINTN gScreenRows; | |
| // Global to turn on/off breaking commands with prompts before they scroll the screen | |
| BOOLEAN gPageBreak = TRUE; | |
| VOID | |
| RingBufferIncrement ( | |
| IN INTN *Value | |
| ) | |
| { | |
| *Value = *Value + 1; | |
| if (*Value >= MAX_CMD_HISTORY) { | |
| *Value = 0; | |
| } | |
| } | |
| VOID | |
| RingBufferDecrement ( | |
| IN INTN *Value | |
| ) | |
| { | |
| *Value = *Value - 1; | |
| if (*Value < 0) { | |
| *Value = MAX_CMD_HISTORY - 1; | |
| } | |
| } | |
| /** | |
| Save this command in the circular history buffer. Older commands are | |
| overwritten with newer commands. | |
| @param Cmd Command line to archive the history of. | |
| @return None | |
| **/ | |
| VOID | |
| SetCmdHistory ( | |
| IN CHAR8 *Cmd | |
| ) | |
| { | |
| // Don't bother adding empty commands to the list | |
| if (AsciiStrLen(Cmd) != 0) { | |
| // First entry | |
| if (mCmdHistoryStart == -1) { | |
| mCmdHistoryStart = 0; | |
| mCmdHistoryEnd = 0; | |
| } else { | |
| // Record the new command at the next index | |
| RingBufferIncrement(&mCmdHistoryStart); | |
| // If the next index runs into the end index, shuffle end back by one | |
| if (mCmdHistoryStart == mCmdHistoryEnd) { | |
| RingBufferIncrement(&mCmdHistoryEnd); | |
| } | |
| } | |
| // Copy the new command line into the ring buffer | |
| AsciiStrnCpy(&mCmdHistory[mCmdHistoryStart][0], Cmd, MAX_CMD_LINE); | |
| } | |
| // Reset the command history for the next up arrow press | |
| mCmdHistoryCurrent = mCmdHistoryStart; | |
| } | |
| /** | |
| Retreave data from the Command History buffer. Direction maps into up arrow | |
| an down arrow on the command line | |
| @param Direction Command forward or back | |
| @return The Command history based on the Direction | |
| **/ | |
| CHAR8 * | |
| GetCmdHistory ( | |
| IN UINT16 Direction | |
| ) | |
| { | |
| CHAR8 *HistoricalCommand = NULL; | |
| // No history yet? | |
| if (mCmdHistoryCurrent == -1) { | |
| HistoricalCommand = mCmdBlank; | |
| goto Exit; | |
| } | |
| if (Direction == SCAN_UP) { | |
| HistoricalCommand = &mCmdHistory[mCmdHistoryCurrent][0]; | |
| // if we just echoed the last command, hang out there, don't wrap around | |
| if (mCmdHistoryCurrent == mCmdHistoryEnd) { | |
| goto Exit; | |
| } | |
| // otherwise, back up by one | |
| RingBufferDecrement(&mCmdHistoryCurrent); | |
| } else if (Direction == SCAN_DOWN) { | |
| // if we last echoed the start command, put a blank prompt out | |
| if (mCmdHistoryCurrent == mCmdHistoryStart) { | |
| HistoricalCommand = mCmdBlank; | |
| goto Exit; | |
| } | |
| // otherwise increment the current pointer and return that command | |
| RingBufferIncrement(&mCmdHistoryCurrent); | |
| RingBufferIncrement(&mCmdHistoryCurrent); | |
| HistoricalCommand = &mCmdHistory[mCmdHistoryCurrent][0]; | |
| RingBufferDecrement(&mCmdHistoryCurrent); | |
| } | |
| Exit: | |
| return HistoricalCommand; | |
| } | |
| /** | |
| Parse the CmdLine and break it up into Argc (arg count) and Argv (array of | |
| pointers to each argument). The Cmd buffer is altered and seperators are | |
| converted to string terminators. This allows Argv to point into CmdLine. | |
| A CmdLine can support multiple commands. The next command in the command line | |
| is returned if it exists. | |
| @param CmdLine String to parse for a set of commands | |
| @param Argc Returns the number of arguments in the CmdLine current command | |
| @param Argv Argc pointers to each string in CmdLine | |
| @return Next Command in the command line or NULL if non exists | |
| **/ | |
| CHAR8 * | |
| ParseArguments ( | |
| IN CHAR8 *CmdLine, | |
| OUT UINTN *Argc, | |
| OUT CHAR8 **Argv | |
| ) | |
| { | |
| UINTN Arg; | |
| CHAR8 *Char; | |
| BOOLEAN LookingForArg; | |
| BOOLEAN InQuote; | |
| *Argc = 0; | |
| if (AsciiStrLen (CmdLine) == 0) { | |
| return NULL; | |
| } | |
| // Walk a single command line. A CMD_SEPERATOR allows mult commands on a single line | |
| InQuote = FALSE; | |
| LookingForArg = TRUE; | |
| for (Char = CmdLine, Arg = 0; *Char != '\0'; Char++) { | |
| if (!InQuote && *Char == CMD_SEPERATOR) { | |
| break; | |
| } | |
| // Perform any text coversion here | |
| if (*Char == '\t') { | |
| // TAB to space | |
| *Char = ' '; | |
| } | |
| if (LookingForArg) { | |
| // Look for the beging of an Argv[] entry | |
| if (*Char == '"') { | |
| Argv[Arg++] = ++Char; | |
| LookingForArg = FALSE; | |
| InQuote = TRUE; | |
| } else if (*Char != ' ') { | |
| Argv[Arg++] = Char; | |
| LookingForArg = FALSE; | |
| } | |
| } else { | |
| // Looking for the terminator of an Argv[] entry | |
| if ((InQuote && (*Char == '"')) || (!InQuote && (*Char == ' '))) { | |
| *Char = '\0'; | |
| LookingForArg = TRUE; | |
| } | |
| } | |
| } | |
| *Argc = Arg; | |
| if (*Char == CMD_SEPERATOR) { | |
| // Replace the command delimeter with null and return pointer to next command line | |
| *Char = '\0'; | |
| return ++Char; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| Return a keypress or optionally timeout if a timeout value was passed in. | |
| An optional callback funciton is called evey second when waiting for a | |
| timeout. | |
| @param Key EFI Key information returned | |
| @param TimeoutInSec Number of seconds to wait to timeout | |
| @param CallBack Callback called every second during the timeout wait | |
| @return EFI_SUCCESS Key was returned | |
| @return EFI_TIMEOUT If the TimoutInSec expired | |
| **/ | |
| EFI_STATUS | |
| EblGetCharKey ( | |
| IN OUT EFI_INPUT_KEY *Key, | |
| IN UINTN TimeoutInSec, | |
| IN EBL_GET_CHAR_CALL_BACK CallBack OPTIONAL | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN WaitCount; | |
| UINTN WaitIndex; | |
| EFI_EVENT WaitList[2]; | |
| WaitCount = 1; | |
| WaitList[0] = gST->ConIn->WaitForKey; | |
| if (TimeoutInSec != 0) { | |
| // Create a time event for 1 sec duration if we have a timeout | |
| gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &WaitList[1]); | |
| gBS->SetTimer (WaitList[1], TimerPeriodic, EFI_SET_TIMER_TO_SECOND); | |
| WaitCount++; | |
| } | |
| for (;;) { | |
| Status = gBS->WaitForEvent (WaitCount, WaitList, &WaitIndex); | |
| ASSERT_EFI_ERROR (Status); | |
| switch (WaitIndex) { | |
| case 0: | |
| // Key event signaled | |
| Status = gST->ConIn->ReadKeyStroke (gST->ConIn, Key); | |
| if (!EFI_ERROR (Status)) { | |
| if (WaitCount == 2) { | |
| gBS->CloseEvent (WaitList[1]); | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| break; | |
| case 1: | |
| // Periodic 1 sec timer signaled | |
| TimeoutInSec--; | |
| if (CallBack != NULL) { | |
| // Call the users callback function if registered | |
| CallBack (TimeoutInSec); | |
| } | |
| if (TimeoutInSec == 0) { | |
| gBS->CloseEvent (WaitList[1]); | |
| return EFI_TIMEOUT; | |
| } | |
| break; | |
| default: | |
| ASSERT (FALSE); | |
| } | |
| } | |
| } | |
| /** | |
| This routine is used prevent command output data from scrolling off the end | |
| of the screen. The global gPageBreak is used to turn on or off this feature. | |
| If the CurrentRow is near the end of the screen pause and print out a prompt | |
| If the use hits Q to quit return TRUE else for any other key return FALSE. | |
| PrefixNewline is used to figure out if a newline is needed before the prompt | |
| string. This depends on the last print done before calling this function. | |
| CurrentRow is updated by one on a call or set back to zero if a prompt is | |
| needed. | |
| @param CurrentRow Used to figure out if its the end of the page and updated | |
| @param PrefixNewline Did previous print issue a newline | |
| @return TRUE if Q was hit to quit, FALSE in all other cases. | |
| **/ | |
| BOOLEAN | |
| EblAnyKeyToContinueQtoQuit ( | |
| IN UINTN *CurrentRow, | |
| IN BOOLEAN PrefixNewline | |
| ) | |
| { | |
| EFI_INPUT_KEY InputKey; | |
| if (!gPageBreak) { | |
| // global disable for this feature | |
| return FALSE; | |
| } | |
| if (*CurrentRow >= (gScreenRows - 2)) { | |
| if (PrefixNewline) { | |
| AsciiPrint ("\n"); | |
| } | |
| AsciiPrint ("Any key to continue (Q to quit): "); | |
| EblGetCharKey (&InputKey, 0, NULL); | |
| AsciiPrint ("\n"); | |
| // Time to promt to stop the screen. We have to leave space for the prompt string | |
| *CurrentRow = 0; | |
| if (InputKey.UnicodeChar == 'Q' || InputKey.UnicodeChar == 'q') { | |
| return TRUE; | |
| } | |
| } else { | |
| *CurrentRow += 1; | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| Set the text color of the EFI Console. If a zero is passed in reset to | |
| default text/background color. | |
| @param Attribute For text and background color | |
| **/ | |
| VOID | |
| EblSetTextColor ( | |
| UINTN Attribute | |
| ) | |
| { | |
| if (Attribute == 0) { | |
| // Set the text color back to default | |
| Attribute = (UINTN)PcdGet32 (PcdEmbeddedDefaultTextColor); | |
| } | |
| gST->ConOut->SetAttribute (gST->ConOut, Attribute); | |
| } | |
| /** | |
| Collect the keyboard input for a cmd line. Carage Return, New Line, or ESC | |
| terminates the command line. You can edit the command line via left arrow, | |
| delete and backspace and they all back up and erase the command line. | |
| No edit of commnad line is possible without deletion at this time! | |
| The up arrow and down arrow fill Cmd with information from the history | |
| buffer. | |
| @param Cmd Command line to return | |
| @param CmdMaxSize Maximum size of Cmd | |
| @return The Status of EblGetCharKey() | |
| **/ | |
| EFI_STATUS | |
| GetCmd ( | |
| IN OUT CHAR8 *Cmd, | |
| IN UINTN CmdMaxSize | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN Index; | |
| UINTN Index2; | |
| CHAR8 Char; | |
| CHAR8 *History; | |
| EFI_INPUT_KEY Key; | |
| for (Index = 0; Index < CmdMaxSize - 1;) { | |
| Status = EblGetCharKey (&Key, 0, NULL); | |
| if (EFI_ERROR (Status)) { | |
| Cmd[Index] = '\0'; | |
| AsciiPrint ("\n"); | |
| return Status; | |
| } | |
| Char = (CHAR8)Key.UnicodeChar; | |
| if ((Char == '\n') || (Char == '\r') || (Char == 0x7f)) { | |
| Cmd[Index] = '\0'; | |
| if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho) == TRUE) { | |
| AsciiPrint ("\n\r"); | |
| } | |
| return EFI_SUCCESS; | |
| } else if ((Char == '\b') || (Key.ScanCode == SCAN_LEFT) || (Key.ScanCode == SCAN_DELETE)){ | |
| if (Index != 0) { | |
| Index--; | |
| // | |
| // Update the display | |
| // | |
| AsciiPrint ("\b \b"); | |
| } | |
| } else if ((Key.ScanCode == SCAN_UP) || Key.ScanCode == SCAN_DOWN) { | |
| History = GetCmdHistory (Key.ScanCode); | |
| // | |
| // Clear display line | |
| // | |
| for (Index2 = 0; Index2 < Index; Index2++) { | |
| AsciiPrint ("\b \b"); | |
| } | |
| AsciiPrint (History); | |
| Index = AsciiStrLen (History); | |
| AsciiStrnCpy (Cmd, History, CmdMaxSize); | |
| } else { | |
| Cmd[Index++] = Char; | |
| if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho) == TRUE) { | |
| AsciiPrint ("%c", Char); | |
| } | |
| } | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Print the boot up banner for the EBL. | |
| **/ | |
| VOID | |
| EblPrintStartupBanner ( | |
| VOID | |
| ) | |
| { | |
| AsciiPrint ("Embedded Boot Loader ("); | |
| EblSetTextColor (EFI_YELLOW); | |
| AsciiPrint ("EBL"); | |
| EblSetTextColor (0); | |
| AsciiPrint (") prototype. Built at %a on %a\n",__TIME__, __DATE__); | |
| AsciiPrint ("THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN 'AS IS' BASIS,\nWITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\n"); | |
| AsciiPrint ("Please send feedback to edk2-devel@lists.sourceforge.net\n"); | |
| } | |
| /** | |
| Print the prompt for the EBL. | |
| **/ | |
| VOID | |
| EblPrompt ( | |
| VOID | |
| ) | |
| { | |
| EblSetTextColor (EFI_YELLOW); | |
| AsciiPrint ((CHAR8 *)PcdGetPtr (PcdEmbeddedPrompt), EfiGetCwd ()); | |
| EblSetTextColor (0); | |
| AsciiPrint ("%a", ">"); | |
| } | |
| /** | |
| Parse a command line and execute the commands. The ; seperator allows | |
| multiple commands for each command line. Stop processing if one of the | |
| commands returns an error. | |
| @param CmdLine Command Line to process. | |
| @param MaxCmdLineSize MaxSize of the Command line | |
| @return EFI status of the Command | |
| **/ | |
| EFI_STATUS | |
| ProcessCmdLine ( | |
| IN CHAR8 *CmdLine, | |
| IN UINTN MaxCmdLineSize | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EBL_COMMAND_TABLE *Cmd; | |
| CHAR8 *Ptr; | |
| UINTN Argc; | |
| CHAR8 *Argv[MAX_ARGS]; | |
| // Parse the command line. The loop processes commands seperated by ; | |
| for (Ptr = CmdLine, Status = EFI_SUCCESS; Ptr != NULL;) { | |
| Ptr = ParseArguments (Ptr, &Argc, Argv); | |
| if (Argc != 0) { | |
| Cmd = EblGetCommand (Argv[0]); | |
| if (Cmd != NULL) { | |
| // Execute the Command! | |
| Status = Cmd->Command (Argc, Argv); | |
| if (Status == EFI_ABORTED) { | |
| // exit command so lets exit | |
| break; | |
| } else if (Status == EFI_TIMEOUT) { | |
| // pause command got imput so don't process any more cmd on this cmd line | |
| break; | |
| } else if (EFI_ERROR (Status)) { | |
| AsciiPrint ("%a returned %r error\n", Cmd->Name, Status); | |
| // if any command fails stop processing CmdLine | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return Status; | |
| } | |
| /** | |
| Embedded Boot Loader (EBL) - A simple EFI command line application for embedded | |
| devices. PcdEmbeddedAutomaticBootCommand is a complied in commnad line that | |
| gets executed automatically. The ; seperator allows multiple commands | |
| for each command line. | |
| @param ImageHandle EFI ImageHandle for this application. | |
| @param SystemTable EFI system table | |
| @return EFI status of the applicaiton | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EdkBootLoaderEntry ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| CHAR8 CmdLine[MAX_CMD_LINE]; | |
| CHAR16 *CommandLineVariable = NULL; | |
| CHAR16 *CommandLineVariableName = L"default-cmdline"; | |
| UINTN CommandLineVariableSize = 0; | |
| EFI_GUID VendorGuid; | |
| // Initialize tables of commnads | |
| EblInitializeCmdTable (); | |
| EblInitializeDeviceCmd (); | |
| EblInitializemdHwDebugCmds (); | |
| EblInitializemdHwIoDebugCmds (); | |
| EblInitializeDirCmd (); | |
| EblInitializeHobCmd (); | |
| EblInitializeScriptCmd (); | |
| EblInitializeExternalCmd (); | |
| EblInitializeNetworkCmd(); | |
| // Disable the 5 minute EFI watchdog time so we don't get automatically reset | |
| gBS->SetWatchdogTimer (0, 0, 0, NULL); | |
| if (FeaturePcdGet (PcdEmbeddedMacBoot)) { | |
| // A MAC will boot in graphics mode, so turn it back to text here | |
| // This protocol was removed from edk2. It is only an edk thing. We need to make our own copy. | |
| // DisableQuietBoot (); | |
| // Enable the biggest output screen size possible | |
| gST->ConOut->SetMode (gST->ConOut, (UINTN)gST->ConOut->Mode->MaxMode - 1); | |
| } | |
| // Save current screen mode | |
| gST->ConOut->QueryMode (gST->ConOut, gST->ConOut->Mode->Mode, &gScreenColumns, &gScreenRows); | |
| EblPrintStartupBanner (); | |
| // Parse command line and handle commands seperated by ; | |
| // The loop prints the prompt gets user input and saves history | |
| // Look for a variable with a default command line, otherwise use the Pcd | |
| ZeroMem(&VendorGuid, sizeof(EFI_GUID)); | |
| Status = gRT->GetVariable(CommandLineVariableName, &VendorGuid, NULL, &CommandLineVariableSize, CommandLineVariable); | |
| if (Status == EFI_BUFFER_TOO_SMALL) { | |
| CommandLineVariable = AllocatePool(CommandLineVariableSize); | |
| Status = gRT->GetVariable(CommandLineVariableName, &VendorGuid, NULL, &CommandLineVariableSize, CommandLineVariable); | |
| if (!EFI_ERROR(Status)) { | |
| UnicodeStrToAsciiStr(CommandLineVariable, CmdLine); | |
| } | |
| FreePool(CommandLineVariable); | |
| } | |
| if (EFI_ERROR(Status)) { | |
| AsciiStrCpy (CmdLine, (CHAR8 *)PcdGetPtr (PcdEmbeddedAutomaticBootCommand)); | |
| } | |
| for (;;) { | |
| Status = ProcessCmdLine (CmdLine, MAX_CMD_LINE); | |
| if (Status == EFI_ABORTED) { | |
| // if a command returns EFI_ABORTED then exit the EBL | |
| EblShutdownExternalCmdTable (); | |
| return EFI_SUCCESS; | |
| } | |
| // get the command line from the user | |
| EblPrompt (); | |
| GetCmd (CmdLine, MAX_CMD_LINE); | |
| SetCmdHistory (CmdLine); | |
| } | |
| } | |