mirror of https://github.com/acidanthera/audk.git
617 lines
16 KiB
C
617 lines
16 KiB
C
/** @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 dev@edk2.tianocore.org\n");
|
|
}
|
|
|
|
|
|
/**
|
|
Print the prompt for the EBL.
|
|
**/
|
|
VOID
|
|
EblPrompt (
|
|
VOID
|
|
)
|
|
{
|
|
EblSetTextColor (EFI_YELLOW);
|
|
AsciiPrint ((CHAR8 *)PcdGetPtr (PcdEmbeddedPrompt));
|
|
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();
|
|
|
|
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);
|
|
|
|
// Disable the 5 minute EFI watchdog time so we don't get automatically reset
|
|
gBS->SetWatchdogTimer (0, 0, 0, NULL);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
|