2 Basic command line parser for EBL (Embedded Boot Loader)
4 Copyright (c) 2007, Intel Corporation. All rights reserved.<BR>
5 Portions copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>
7 This program and the accompanying materials
8 are licensed and made available under the terms and conditions of the BSD License
9 which accompanies this distribution. The full text of the license may be found at
10 http://opensource.org/licenses/bsd-license.php
12 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
13 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
20 // Globals for command history processing
21 INTN mCmdHistoryEnd
= -1;
22 INTN mCmdHistoryStart
= -1;
23 INTN mCmdHistoryCurrent
= -1;
24 CHAR8 mCmdHistory
[MAX_CMD_HISTORY
][MAX_CMD_LINE
];
25 CHAR8
*mCmdBlank
= "";
27 // Globals to remember current screen geometry
31 // Global to turn on/off breaking commands with prompts before they scroll the screen
32 BOOLEAN gPageBreak
= TRUE
;
41 if (*Value
>= MAX_CMD_HISTORY
) {
54 *Value
= MAX_CMD_HISTORY
- 1;
59 Save this command in the circular history buffer. Older commands are
60 overwritten with newer commands.
62 @param Cmd Command line to archive the history of.
72 // Don't bother adding empty commands to the list
73 if (AsciiStrLen(Cmd
) != 0) {
76 if (mCmdHistoryStart
== -1) {
80 // Record the new command at the next index
81 RingBufferIncrement(&mCmdHistoryStart
);
83 // If the next index runs into the end index, shuffle end back by one
84 if (mCmdHistoryStart
== mCmdHistoryEnd
) {
85 RingBufferIncrement(&mCmdHistoryEnd
);
89 // Copy the new command line into the ring buffer
90 AsciiStrnCpy(&mCmdHistory
[mCmdHistoryStart
][0], Cmd
, MAX_CMD_LINE
);
93 // Reset the command history for the next up arrow press
94 mCmdHistoryCurrent
= mCmdHistoryStart
;
99 Retreave data from the Command History buffer. Direction maps into up arrow
100 an down arrow on the command line
102 @param Direction Command forward or back
104 @return The Command history based on the Direction
112 CHAR8
*HistoricalCommand
= NULL
;
115 if (mCmdHistoryCurrent
== -1) {
116 HistoricalCommand
= mCmdBlank
;
120 if (Direction
== SCAN_UP
) {
121 HistoricalCommand
= &mCmdHistory
[mCmdHistoryCurrent
][0];
123 // if we just echoed the last command, hang out there, don't wrap around
124 if (mCmdHistoryCurrent
== mCmdHistoryEnd
) {
128 // otherwise, back up by one
129 RingBufferDecrement(&mCmdHistoryCurrent
);
131 } else if (Direction
== SCAN_DOWN
) {
133 // if we last echoed the start command, put a blank prompt out
134 if (mCmdHistoryCurrent
== mCmdHistoryStart
) {
135 HistoricalCommand
= mCmdBlank
;
139 // otherwise increment the current pointer and return that command
140 RingBufferIncrement(&mCmdHistoryCurrent
);
141 RingBufferIncrement(&mCmdHistoryCurrent
);
143 HistoricalCommand
= &mCmdHistory
[mCmdHistoryCurrent
][0];
144 RingBufferDecrement(&mCmdHistoryCurrent
);
148 return HistoricalCommand
;
153 Parse the CmdLine and break it up into Argc (arg count) and Argv (array of
154 pointers to each argument). The Cmd buffer is altered and seperators are
155 converted to string terminators. This allows Argv to point into CmdLine.
156 A CmdLine can support multiple commands. The next command in the command line
157 is returned if it exists.
159 @param CmdLine String to parse for a set of commands
160 @param Argc Returns the number of arguments in the CmdLine current command
161 @param Argv Argc pointers to each string in CmdLine
163 @return Next Command in the command line or NULL if non exists
174 BOOLEAN LookingForArg
;
178 if (AsciiStrLen (CmdLine
) == 0) {
182 // Walk a single command line. A CMD_SEPERATOR allows mult commands on a single line
184 LookingForArg
= TRUE
;
185 for (Char
= CmdLine
, Arg
= 0; *Char
!= '\0'; Char
++) {
186 if (!InQuote
&& *Char
== CMD_SEPERATOR
) {
190 // Perform any text conversion here
197 // Look for the beging of an Argv[] entry
199 Argv
[Arg
++] = ++Char
;
200 LookingForArg
= FALSE
;
202 } else if (*Char
!= ' ') {
204 LookingForArg
= FALSE
;
207 // Looking for the terminator of an Argv[] entry
208 if (!InQuote
&& (*Char
== ' ')) {
210 LookingForArg
= TRUE
;
211 } else if (!InQuote
&& (*Char
== '"') && (*(Char
-1) != '\\')) {
213 } else if (InQuote
&& (*Char
== '"') && (*(Char
-1) != '\\')) {
222 if (*Char
== CMD_SEPERATOR
) {
223 // Replace the command delimeter with null and return pointer to next command line
233 Return a keypress or optionally timeout if a timeout value was passed in.
234 An optional callback funciton is called evey second when waiting for a
237 @param Key EFI Key information returned
238 @param TimeoutInSec Number of seconds to wait to timeout
239 @param CallBack Callback called every second during the timeout wait
241 @return EFI_SUCCESS Key was returned
242 @return EFI_TIMEOUT If the TimoutInSec expired
247 IN OUT EFI_INPUT_KEY
*Key
,
248 IN UINTN TimeoutInSec
,
249 IN EBL_GET_CHAR_CALL_BACK CallBack OPTIONAL
255 EFI_EVENT WaitList
[2];
258 WaitList
[0] = gST
->ConIn
->WaitForKey
;
259 if (TimeoutInSec
!= 0) {
260 // Create a time event for 1 sec duration if we have a timeout
261 gBS
->CreateEvent (EVT_TIMER
, 0, NULL
, NULL
, &WaitList
[1]);
262 gBS
->SetTimer (WaitList
[1], TimerPeriodic
, EFI_SET_TIMER_TO_SECOND
);
267 Status
= gBS
->WaitForEvent (WaitCount
, WaitList
, &WaitIndex
);
268 ASSERT_EFI_ERROR (Status
);
272 // Key event signaled
273 Status
= gST
->ConIn
->ReadKeyStroke (gST
->ConIn
, Key
);
274 if (!EFI_ERROR (Status
)) {
275 if (WaitCount
== 2) {
276 gBS
->CloseEvent (WaitList
[1]);
283 // Periodic 1 sec timer signaled
285 if (CallBack
!= NULL
) {
286 // Call the users callback function if registered
287 CallBack (TimeoutInSec
);
289 if (TimeoutInSec
== 0) {
290 gBS
->CloseEvent (WaitList
[1]);
302 This routine is used prevent command output data from scrolling off the end
303 of the screen. The global gPageBreak is used to turn on or off this feature.
304 If the CurrentRow is near the end of the screen pause and print out a prompt
305 If the use hits Q to quit return TRUE else for any other key return FALSE.
306 PrefixNewline is used to figure out if a newline is needed before the prompt
307 string. This depends on the last print done before calling this function.
308 CurrentRow is updated by one on a call or set back to zero if a prompt is
311 @param CurrentRow Used to figure out if its the end of the page and updated
312 @param PrefixNewline Did previous print issue a newline
314 @return TRUE if Q was hit to quit, FALSE in all other cases.
318 EblAnyKeyToContinueQtoQuit (
319 IN UINTN
*CurrentRow
,
320 IN BOOLEAN PrefixNewline
323 EFI_INPUT_KEY InputKey
;
326 // global disable for this feature
330 if (*CurrentRow
>= (gScreenRows
- 2)) {
334 AsciiPrint ("Any key to continue (Q to quit): ");
335 EblGetCharKey (&InputKey
, 0, NULL
);
338 // Time to promt to stop the screen. We have to leave space for the prompt string
340 if (InputKey
.UnicodeChar
== 'Q' || InputKey
.UnicodeChar
== 'q') {
352 Set the text color of the EFI Console. If a zero is passed in reset to
353 default text/background color.
355 @param Attribute For text and background color
363 if (Attribute
== 0) {
364 // Set the text color back to default
365 Attribute
= (UINTN
)PcdGet32 (PcdEmbeddedDefaultTextColor
);
368 gST
->ConOut
->SetAttribute (gST
->ConOut
, Attribute
);
373 Collect the keyboard input for a cmd line. Carage Return, New Line, or ESC
374 terminates the command line. You can edit the command line via left arrow,
375 delete and backspace and they all back up and erase the command line.
376 No edit of commnad line is possible without deletion at this time!
377 The up arrow and down arrow fill Cmd with information from the history
380 @param Cmd Command line to return
381 @param CmdMaxSize Maximum size of Cmd
383 @return The Status of EblGetCharKey()
399 for (Index
= 0; Index
< CmdMaxSize
- 1;) {
400 Status
= EblGetCharKey (&Key
, 0, NULL
);
401 if (EFI_ERROR (Status
)) {
407 Char
= (CHAR8
)Key
.UnicodeChar
;
408 if ((Char
== '\n') || (Char
== '\r') || (Char
== 0x7f)) {
410 if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho
) == TRUE
) {
414 } else if ((Char
== '\b') || (Key
.ScanCode
== SCAN_LEFT
) || (Key
.ScanCode
== SCAN_DELETE
)){
418 // Update the display
420 AsciiPrint ("\b \b");
422 } else if ((Key
.ScanCode
== SCAN_UP
) || Key
.ScanCode
== SCAN_DOWN
) {
423 History
= GetCmdHistory (Key
.ScanCode
);
425 // Clear display line
427 for (Index2
= 0; Index2
< Index
; Index2
++) {
428 AsciiPrint ("\b \b");
430 AsciiPrint (History
);
431 Index
= AsciiStrLen (History
);
432 AsciiStrnCpy (Cmd
, History
, CmdMaxSize
);
435 if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho
) == TRUE
) {
436 AsciiPrint ("%c", Char
);
446 Print the boot up banner for the EBL.
449 EblPrintStartupBanner (
453 AsciiPrint ("Embedded Boot Loader (");
454 EblSetTextColor (EFI_YELLOW
);
457 AsciiPrint (") prototype. Built at %a on %a\n",__TIME__
, __DATE__
);
458 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");
459 AsciiPrint ("Please send feedback to edk2-devel@lists.sourceforge.net\n");
464 Send null requests to all removable media block IO devices so the a media add/remove/change
465 can be detected in real before we execute a command.
467 This is mainly due to the fact that the FAT driver does not do this today so you can get stale
468 dir commands after an SD Card has been removed.
471 EblProbeRemovableMedia (
480 // Probe for media insertion/removal in removable media devices
482 Max
= EfiGetDeviceCounts (EfiOpenBlockIo
);
484 for (Index
= 0; Index
< Max
; Index
++) {
485 File
= EfiDeviceOpenByType (EfiOpenBlockIo
, Index
);
487 if (File
->FsBlockIoMedia
->RemovableMedia
) {
488 // Probe to see if media is present (or not) or media changed
489 // this causes the ReinstallProtocolInterface() to fire in the
490 // block io driver to update the system about media change events
491 File
->FsBlockIo
->ReadBlocks (File
->FsBlockIo
, File
->FsBlockIo
->Media
->MediaId
, (EFI_LBA
)0, 0, NULL
);
503 Print the prompt for the EBL.
510 EblSetTextColor (EFI_YELLOW
);
511 AsciiPrint ("%a %a",(CHAR8
*)PcdGetPtr (PcdEmbeddedPrompt
), EfiGetCwd ());
513 AsciiPrint ("%a", ">");
519 Parse a command line and execute the commands. The ; seperator allows
520 multiple commands for each command line. Stop processing if one of the
521 commands returns an error.
523 @param CmdLine Command Line to process.
524 @param MaxCmdLineSize MaxSize of the Command line
526 @return EFI status of the Command
532 IN UINTN MaxCmdLineSize
536 EBL_COMMAND_TABLE
*Cmd
;
539 CHAR8
*Argv
[MAX_ARGS
];
541 // Parse the command line. The loop processes commands seperated by ;
542 for (Ptr
= CmdLine
, Status
= EFI_SUCCESS
; Ptr
!= NULL
;) {
543 Ptr
= ParseArguments (Ptr
, &Argc
, Argv
);
545 Cmd
= EblGetCommand (Argv
[0]);
547 // Execute the Command!
548 Status
= Cmd
->Command (Argc
, Argv
);
549 if (Status
== EFI_ABORTED
) {
550 // exit command so lets exit
552 } else if (Status
== EFI_TIMEOUT
) {
553 // pause command got imput so don't process any more cmd on this cmd line
555 } else if (EFI_ERROR (Status
)) {
556 AsciiPrint ("%a returned %r error\n", Cmd
->Name
, Status
);
557 // if any command fails stop processing CmdLine
570 Embedded Boot Loader (EBL) - A simple EFI command line application for embedded
571 devices. PcdEmbeddedAutomaticBootCommand is a complied in commnad line that
572 gets executed automatically. The ; seperator allows multiple commands
573 for each command line.
575 @param ImageHandle EFI ImageHandle for this application.
576 @param SystemTable EFI system table
578 @return EFI status of the applicaiton
584 IN EFI_HANDLE ImageHandle
,
585 IN EFI_SYSTEM_TABLE
*SystemTable
589 CHAR8 CmdLine
[MAX_CMD_LINE
];
590 CHAR16
*CommandLineVariable
= NULL
;
591 CHAR16
*CommandLineVariableName
= L
"default-cmdline";
592 UINTN CommandLineVariableSize
= 0;
595 // Initialize tables of commnads
596 EblInitializeCmdTable ();
597 EblInitializeDeviceCmd ();
598 EblInitializemdHwDebugCmds ();
599 EblInitializemdHwIoDebugCmds ();
600 EblInitializeDirCmd ();
601 EblInitializeHobCmd ();
602 EblInitializeScriptCmd ();
603 EblInitializeExternalCmd ();
604 EblInitializeNetworkCmd();
605 EblInitializeVariableCmds ();
607 // Disable the 5 minute EFI watchdog time so we don't get automatically reset
608 gBS
->SetWatchdogTimer (0, 0, 0, NULL
);
610 if (FeaturePcdGet (PcdEmbeddedMacBoot
)) {
611 // A MAC will boot in graphics mode, so turn it back to text here
612 // This protocol was removed from edk2. It is only an edk thing. We need to make our own copy.
613 // DisableQuietBoot ();
615 // Enable the biggest output screen size possible
616 gST
->ConOut
->SetMode (gST
->ConOut
, (UINTN
)gST
->ConOut
->Mode
->MaxMode
- 1);
620 // Save current screen mode
621 gST
->ConOut
->QueryMode (gST
->ConOut
, gST
->ConOut
->Mode
->Mode
, &gScreenColumns
, &gScreenRows
);
623 EblPrintStartupBanner ();
625 // Parse command line and handle commands seperated by ;
626 // The loop prints the prompt gets user input and saves history
628 // Look for a variable with a default command line, otherwise use the Pcd
629 ZeroMem(&VendorGuid
, sizeof(EFI_GUID
));
631 Status
= gRT
->GetVariable(CommandLineVariableName
, &VendorGuid
, NULL
, &CommandLineVariableSize
, CommandLineVariable
);
632 if (Status
== EFI_BUFFER_TOO_SMALL
) {
633 CommandLineVariable
= AllocatePool(CommandLineVariableSize
);
635 Status
= gRT
->GetVariable(CommandLineVariableName
, &VendorGuid
, NULL
, &CommandLineVariableSize
, CommandLineVariable
);
636 if (!EFI_ERROR(Status
)) {
637 UnicodeStrToAsciiStr(CommandLineVariable
, CmdLine
);
640 FreePool(CommandLineVariable
);
643 if (EFI_ERROR(Status
)) {
644 AsciiStrCpy (CmdLine
, (CHAR8
*)PcdGetPtr (PcdEmbeddedAutomaticBootCommand
));
648 Status
= ProcessCmdLine (CmdLine
, MAX_CMD_LINE
);
649 if (Status
== EFI_ABORTED
) {
650 // if a command returns EFI_ABORTED then exit the EBL
651 EblShutdownExternalCmdTable ();
655 // get the command line from the user
657 GetCmd (CmdLine
, MAX_CMD_LINE
);
658 SetCmdHistory (CmdLine
);
660 if (FeaturePcdGet (PcdEmbeddedProbeRemovable
)) {
661 // Probe removable media devices to see if media has been inserted or removed.
662 EblProbeRemovableMedia ();