You've already forked slimbootloader
mirror of
https://github.com/Dasharo/slimbootloader.git
synced 2026-03-06 15:26:20 -08:00
This patch allows both 32/64-bit addressing properly. - Pointer type cast with UINTN - Add missing EFIAPI for APIs Signed-off-by: Aiden Park <aiden.park@intel.com>
650 lines
15 KiB
C
650 lines
15 KiB
C
/** @file
|
|
A minimal command-line shell.
|
|
|
|
Copyright (c) 2017 - 2019, Intel Corporation. All rights reserved.<BR>
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
**/
|
|
|
|
#include <Library/ShellLib.h>
|
|
#include <Library/ConsoleInLib.h>
|
|
#include <Library/ConsoleOutLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/TimerLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/SortLib.h>
|
|
#include "Shell.h"
|
|
#include "History.h"
|
|
#include "Parsing.h"
|
|
#include "ShellCmds.h"
|
|
|
|
#define ESC '\x1b'
|
|
|
|
/**
|
|
Prompt user for command, receive command, run command.
|
|
|
|
@param[in] Shell shell instance
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval RETURN_ABORTED
|
|
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
ShellPrompt (
|
|
IN SHELL *Shell
|
|
);
|
|
|
|
/**
|
|
Find shell command by name.
|
|
|
|
@param[in] Shell shell instance
|
|
@param[in] Name name of command
|
|
@param[in] Ptr pointer to string pointer
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval EFI_NOT_FOUND
|
|
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
FindShellCommand (
|
|
IN SHELL *Shell,
|
|
IN CONST CHAR16 *Name,
|
|
IN CONST SHELL_COMMAND **Ptr
|
|
);
|
|
|
|
/**
|
|
Process and run a command line.
|
|
|
|
@param[in] Shell shell instance
|
|
@param[in] CmdLine command line to parse
|
|
|
|
@retval EFI_SUCCESS command was completed
|
|
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
RunShellCommand (
|
|
IN SHELL *Shell,
|
|
IN CONST CHAR16 *CmdLine
|
|
);
|
|
|
|
/**
|
|
Wait for command line input from the serial port.
|
|
|
|
@param[in] Shell shell instance
|
|
@param[out] Buffer buffer to receive command line
|
|
@param[in] BufferSize size (in bytes) of the buffer
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval EFI_BUFFER_TOO_SMALL
|
|
@retval EFI_TIMEOUT
|
|
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
ReadShellCommand (
|
|
IN SHELL *Shell,
|
|
OUT CHAR16 *Buffer,
|
|
IN CONST UINTN BufferSize
|
|
);
|
|
|
|
/**
|
|
Begin a run-time interactive shell.
|
|
|
|
@param[in] Timeout seconds to wait for input before returning (0 for no timeout)
|
|
|
|
@retval EFI_SUCCESS
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
Shell (
|
|
IN UINTN Timeout
|
|
)
|
|
{
|
|
BOOLEAN Start;
|
|
UINTN Index, Index1;
|
|
SHELL Shell;
|
|
UINT8 Buffer;
|
|
|
|
ZeroMem (&Shell, sizeof (Shell));
|
|
InitializeListHead (&Shell.CommandEntryList);
|
|
|
|
Shell.CommandLineMaxLen = FeaturePcdGet (PcdMiniShellEnabled) ? \
|
|
MAX_COMMAND_LINE_LEN_MINI_SHELL : MAX_COMMAND_LINE_LEN;
|
|
|
|
LoadShellCommands (&Shell);
|
|
Shell.ShouldExit = FALSE;
|
|
|
|
if (Timeout != 0) {
|
|
ShellPrint (L"\n");
|
|
while (ConsolePoll ()) {
|
|
ConsoleRead (&Buffer, 1);
|
|
}
|
|
for (Index = Timeout; Index > 0; Index--) {
|
|
ShellPrint (L"Press any key within %d second(s) to enter the command shell", Index);
|
|
for (Index1 = 0; Index1 < 10; Index1++) {
|
|
Start = ConsolePoll ();
|
|
if (Start) {
|
|
break;
|
|
}
|
|
MicroSecondDelay (100 * 1000);
|
|
}
|
|
ShellPrint(L"\r");
|
|
if (Start) {
|
|
break;
|
|
}
|
|
}
|
|
ShellPrint (L"\n");
|
|
if (!Start) {
|
|
return EFI_ABORTED;
|
|
}
|
|
}
|
|
|
|
HistoryInit (&Shell, TRUE);
|
|
|
|
while (! (Shell.ShouldExit)) {
|
|
ShellPrompt (&Shell);
|
|
}
|
|
|
|
HistoryInit (&Shell, FALSE);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Prompt user for command, receive command, run command.
|
|
|
|
@param[in] Shell shell instance
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval RETURN_ABORTED
|
|
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
ShellPrompt (
|
|
IN SHELL *Shell
|
|
)
|
|
{
|
|
CHAR16 CommandLine[MAX_COMMAND_LINE_LEN];
|
|
EFI_STATUS Status;
|
|
|
|
ASSERT (Shell != NULL);
|
|
ShellPrint (L"\nShell> ");
|
|
|
|
Status = ReadShellCommand (Shell, CommandLine, Shell->CommandLineMaxLen * sizeof (CHAR16));
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
HistoryAdd (Shell, CommandLine);
|
|
|
|
return RunShellCommand (Shell, CommandLine);
|
|
}
|
|
|
|
/**
|
|
Wait for command line input from the serial port.
|
|
|
|
@param[in] Shell shell instance
|
|
@param[out] Buffer buffer to receive command line
|
|
@param[in] BufferSize size (in bytes) of the buffer
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval EFI_BUFFER_TOO_SMALL
|
|
@retval EFI_TIMEOUT
|
|
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
ReadShellCommand (
|
|
IN SHELL *Shell,
|
|
OUT CHAR16 *Buffer,
|
|
IN CONST UINTN BufferSize
|
|
)
|
|
{
|
|
UINTN Count;
|
|
UINTN CurrCount;
|
|
UINT8 Char;
|
|
UINTN HistoryLineLen;
|
|
CHAR16 HistoryLine[MAX_COMMAND_LINE_LEN];
|
|
|
|
Count = 0;
|
|
|
|
ASSERT (Buffer != NULL);
|
|
ASSERT (BufferSize > sizeof (CHAR16));
|
|
|
|
while (1) {
|
|
// Check for buffer overflow
|
|
if (Count >= (BufferSize / sizeof (CHAR16) - 1)) {
|
|
Buffer[0] = CHAR_NULL; // Terminate String
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
// Get next character from serial port
|
|
Char = 0;
|
|
if (ConsoleRead (&Char, 1) < 1) {
|
|
return EFI_TIMEOUT;
|
|
}
|
|
|
|
// Check for end of line
|
|
if ((Char == '\r') || (Char == '\n')) {
|
|
Buffer[Count] = CHAR_NULL; // Terminate String
|
|
break;
|
|
}
|
|
|
|
// Check for backspace/delete
|
|
if ((Char == 0x7f) || (Char == '\b')) {
|
|
if (Count > 0) {
|
|
// Move cursor back 1 char and clear
|
|
ShellPrint (L"\b \b");
|
|
Count--;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Handle key codes of the form: ESC [ x
|
|
if (Char == ESC) {
|
|
// Expect '[' character
|
|
if (ConsoleRead (&Char, 1) < 1) {
|
|
return EFI_TIMEOUT;
|
|
}
|
|
if (Char == '[') {
|
|
// Expect command
|
|
if (ConsoleRead (&Char, 1) < 1) {
|
|
return EFI_TIMEOUT;
|
|
}
|
|
|
|
HistoryLineLen = 0;
|
|
if (Char == 'A') { // Up
|
|
HistoryLineLen = HistoryUp (Shell, HistoryLine);
|
|
} else if (Char == 'B') { // Down
|
|
HistoryLineLen = HistoryDown (Shell, HistoryLine);
|
|
} else if (Char == 'C') { // Right
|
|
} else if (Char == 'D') { // Left
|
|
}
|
|
|
|
|
|
CurrCount = Count;
|
|
while (Count-- > 0) {
|
|
ShellPrint (L"\b");
|
|
}
|
|
if (HistoryLineLen > 0) {
|
|
StrCpyS (Buffer, Shell->CommandLineMaxLen, HistoryLine);
|
|
ShellPrint (L"%s", Buffer);
|
|
}
|
|
if (CurrCount > HistoryLineLen) {
|
|
CurrCount = CurrCount - HistoryLineLen;
|
|
|
|
Count = CurrCount;
|
|
while (Count-- > 0) {
|
|
ShellPrint (L" ");
|
|
}
|
|
|
|
Count = CurrCount;
|
|
while (Count-- > 0) {
|
|
ShellPrint (L"\b");
|
|
}
|
|
}
|
|
Count = HistoryLineLen;
|
|
|
|
// Key code consumed, continue getting input
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Echo character back and save character in buffer
|
|
ShellPrint (L"%c", Char);
|
|
Buffer[Count++] = Char;
|
|
}
|
|
|
|
ShellPrint (L"\n");
|
|
TrimSpaces (&Buffer);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Read a line of input from the serial port.
|
|
|
|
@param[in] Shell shell instance
|
|
@param[out] Buffer buffer to receive command line
|
|
@param[in] BufferSize size (in bytes) of the buffer
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval EFI_BUFFER_TOO_SMALL
|
|
@retval EFI_TIMEOUT
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ShellReadLine (
|
|
IN SHELL *Shell,
|
|
OUT CHAR16 *Buffer,
|
|
IN CONST UINTN BufferSize
|
|
)
|
|
{
|
|
UINTN Count;
|
|
UINT8 Char;
|
|
|
|
Count = 0;
|
|
|
|
ASSERT (Buffer != NULL);
|
|
ASSERT (BufferSize > sizeof (CHAR16));
|
|
|
|
while (1) {
|
|
// Check for buffer overflow
|
|
if (Count >= (BufferSize / sizeof (CHAR16) - 1)) {
|
|
Buffer[0] = CHAR_NULL; // Terminate String
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
// Get next character from serial port
|
|
Char = 0;
|
|
if (ConsoleRead (&Char, 1) < 1) {
|
|
return EFI_TIMEOUT;
|
|
}
|
|
|
|
// Check for end of line
|
|
if ((Char == '\r') || (Char == '\n')) {
|
|
Buffer[Count] = CHAR_NULL; // Terminate String
|
|
break;
|
|
}
|
|
|
|
// Check for backspace/delete
|
|
if ((Char == 0x7f) || (Char == '\b')) {
|
|
if (Count > 0) {
|
|
// Move cursor back 1 char and clear
|
|
ShellPrint (L"\b \b");
|
|
Count--;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Handle key codes of the form: ESC [ x
|
|
if (Char == ESC) {
|
|
// Expect '[' character
|
|
if (ConsoleRead (&Char, 1) < 1) {
|
|
return EFI_TIMEOUT;
|
|
}
|
|
if (Char == '[') {
|
|
// Expect command
|
|
if (ConsoleRead (&Char, 1) < 1) {
|
|
return EFI_TIMEOUT;
|
|
}
|
|
|
|
if (Char == 'A') { // Up
|
|
} else if (Char == 'B') { // Down
|
|
} else if (Char == 'C') { // Right
|
|
} else if (Char == 'D') { // Left
|
|
}
|
|
|
|
// Key code consumed, continue getting input
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Echo character back and save character in buffer
|
|
ShellPrint (L"%c", Char);
|
|
Buffer[Count++] = Char;
|
|
}
|
|
|
|
ShellPrint (L"\n");
|
|
TrimSpaces (&Buffer);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Read a UINT value from the serial port.
|
|
|
|
@param[in] Shell shell instance
|
|
@param[out] Buffer buffer to receive command line
|
|
@param[in] BufferSize size (in bytes) of the buffer
|
|
@param[out] IsHex determine if the UINT is hex or decimal format
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval EFI_BUFFER_TOO_SMALL
|
|
@retval EFI_TIMEOUT
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ShellReadUintn (
|
|
IN SHELL *Shell,
|
|
OUT CHAR16 *Buffer,
|
|
IN CONST UINTN BufferSize,
|
|
OUT BOOLEAN *IsHex
|
|
)
|
|
{
|
|
UINTN Count;
|
|
UINT8 Char;
|
|
|
|
Count = 0;
|
|
|
|
ASSERT (Buffer != NULL);
|
|
ASSERT (BufferSize > sizeof (CHAR16));
|
|
|
|
*IsHex = FALSE;
|
|
ZeroMem (Buffer, BufferSize);
|
|
|
|
while (1) {
|
|
// Check for buffer overflow
|
|
if (Count >= (BufferSize / sizeof (CHAR16) - 1)) {
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
// Get next character from serial port
|
|
Char = 0;
|
|
if (ConsoleRead (&Char, 1) < 1) {
|
|
return EFI_TIMEOUT;
|
|
}
|
|
|
|
if ((Char == '\r') || (Char == '\n')) {
|
|
// Check for end of line
|
|
Buffer[Count] = CHAR_NULL; // Terminate String
|
|
break;
|
|
} else if ((Char == 0x7f) || (Char == '\b')) {
|
|
// Check for backspace/delete
|
|
if (Count > 0) {
|
|
if (Buffer[Count - 1] == 'x' || Buffer[Count - 1] == 'X') {
|
|
*IsHex = FALSE;
|
|
}
|
|
// Move cursor back 1 char and clear
|
|
ShellPrint (L"\b \b");
|
|
Count--;
|
|
}
|
|
continue;
|
|
} else if (Char == 'x' || Char == 'X') {
|
|
if (Count != 1) {
|
|
continue;
|
|
}
|
|
*IsHex = TRUE;
|
|
} else if (Char >= '0' && Char <= '9') {
|
|
;
|
|
} else if (*IsHex && ((Char >= 'a' && Char <= 'f') || (Char >= 'A' && Char <= 'F'))) {
|
|
;
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
// Echo character back and save character in buffer
|
|
ShellPrint (L"%c", Char);
|
|
Buffer[Count++] = Char;
|
|
}
|
|
|
|
ShellPrint (L"\n");
|
|
TrimSpaces (&Buffer);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Process and run a command line.
|
|
|
|
@param[in] Shell shell instance
|
|
@param[in] CmdLine command line to parse
|
|
|
|
@retval EFI_SUCCESS command was completed
|
|
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
RunShellCommand (
|
|
IN SHELL *Shell,
|
|
IN CONST CHAR16 *CmdLine
|
|
)
|
|
{
|
|
CHAR16 Buf[MAX_COMMAND_LINE_LEN];
|
|
CHAR16 *Ptr;
|
|
CONST CHAR16 *Walker;
|
|
UINTN Index, Count, BufRemaining;
|
|
EFI_STATUS Status;
|
|
CONST SHELL_COMMAND *Cmd;
|
|
CHAR16 *Argv[8]; // FIXME: Use allocator once that's available
|
|
|
|
// Parse the command line parameters
|
|
BufRemaining = Shell->CommandLineMaxLen * sizeof(CHAR16);
|
|
Ptr = Buf;
|
|
Walker = CmdLine;
|
|
|
|
for (Count = 0; Walker != NULL && *Walker != CHAR_NULL; Count++) {
|
|
Status = GetNextParameter (&Walker, &Ptr, BufRemaining, TRUE);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
BufRemaining -= StrSize (Ptr);
|
|
Ptr += StrLen (Ptr) + 1;
|
|
}
|
|
|
|
if (Count == 0) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
ASSERT (Count <= (sizeof (Argv) / sizeof (Argv[0])));
|
|
for (Index = 0, Ptr = Buf; Index < Count; Index++) {
|
|
Argv[Index] = Ptr;
|
|
Ptr += StrLen (Ptr) + 1;
|
|
}
|
|
|
|
// Locate and execute command
|
|
Cmd = NULL;
|
|
Status = FindShellCommand (Shell, Argv[0], &Cmd);
|
|
if (EFI_ERROR (Status)) {
|
|
if (Status == EFI_NOT_FOUND) {
|
|
ShellPrint (L"Command not found (try help)\n");
|
|
}
|
|
} else {
|
|
ASSERT (Cmd);
|
|
Status = Cmd->Entry (Shell, Count, Argv);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Find shell command by name.
|
|
|
|
@param[in] Shell shell instance
|
|
@param[in] Name name of command
|
|
@param[in] Ptr pointer to string pointer
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval EFI_NOT_FOUND
|
|
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
FindShellCommand (
|
|
IN SHELL *Shell,
|
|
IN CONST CHAR16 *Name,
|
|
IN CONST SHELL_COMMAND **Ptr
|
|
)
|
|
{
|
|
LIST_ENTRY *Link;
|
|
SHELL_COMMAND_LIST_ENTRY *Entry;
|
|
LIST_ENTRY *EntryList;
|
|
|
|
//
|
|
// Add '?' alias for help
|
|
//
|
|
if (Name[0] == '?') {
|
|
Name = L"help";
|
|
}
|
|
|
|
EntryList = &Shell->CommandEntryList;
|
|
for (Link = EntryList->ForwardLink; Link != EntryList; Link = Link->ForwardLink) {
|
|
Entry = CR (Link, SHELL_COMMAND_LIST_ENTRY, Link, SHELL_COMMAND_LIST_ENTRY_SIGNATURE);
|
|
if (StrCmp (Name, Entry->ShellCommand->Name) == 0) {
|
|
*Ptr = Entry->ShellCommand;
|
|
return EFI_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
/**
|
|
The function is called by PerformInsertionSortList to sort Shell commands by its name.
|
|
|
|
@param[in] Buffer1 The pointer to first list entry.
|
|
@param[in] Buffer2 The pointer to second list entry.
|
|
|
|
@retval 0 Buffer1 name is less than Buffer2 name.
|
|
@retval 1 Buffer1 name is greater than or equal to Buffer2 name.
|
|
|
|
**/
|
|
INTN
|
|
EFIAPI
|
|
CompareCommandEntry (
|
|
IN CONST VOID *Buffer1,
|
|
IN CONST VOID *Buffer2
|
|
)
|
|
{
|
|
SHELL_COMMAND_LIST_ENTRY *NewEntry;
|
|
SHELL_COMMAND_LIST_ENTRY *CurrEntry;
|
|
|
|
NewEntry = BASE_CR (Buffer1, SHELL_COMMAND_LIST_ENTRY, Link);
|
|
CurrEntry = BASE_CR (Buffer2, SHELL_COMMAND_LIST_ENTRY, Link);
|
|
|
|
//
|
|
// Ascending Order
|
|
//
|
|
return StrCmp (NewEntry->ShellCommand->Name, CurrEntry->ShellCommand->Name) >= 0 ? 1: 0;
|
|
}
|
|
|
|
/**
|
|
Register a Shell Command
|
|
|
|
@param[in] Shell Shell Context
|
|
@param[in] ShellCommand A Shell Command to be registered
|
|
|
|
@retval EFI_SUCCESS
|
|
@retval EFI_OUT_OF_RESOURCES
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ShellCommandRegister (
|
|
IN SHELL *Shell,
|
|
IN CONST SHELL_COMMAND *ShellCommand
|
|
)
|
|
{
|
|
SHELL_COMMAND_LIST_ENTRY *Entry;
|
|
LIST_ENTRY *EntryList;
|
|
|
|
Entry = (SHELL_COMMAND_LIST_ENTRY *)AllocateZeroPool (sizeof (SHELL_COMMAND_LIST_ENTRY));
|
|
if (Entry == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
InitializeListHead (&Entry->Link);
|
|
Entry->ShellCommand = ShellCommand;
|
|
Entry->Signature = SHELL_COMMAND_LIST_ENTRY_SIGNATURE;
|
|
|
|
EntryList = &Shell->CommandEntryList;
|
|
PerformInsertionSortList (EntryList, &Entry->Link, CompareCommandEntry);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|