1/** @file
2  Basic command line parser for EBL (Embedded Boot Loader)
3
4  Copyright (c) 2007, Intel Corporation. All rights reserved.<BR>
5  Portions copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>
6  (C) Copyright 2015 Hewlett Packard Enterprise Development LP<BR>
7
8  This program and the accompanying materials
9  are licensed and made available under the terms and conditions of the BSD License
10  which accompanies this distribution.  The full text of the license may be found at
11  http://opensource.org/licenses/bsd-license.php
12
13  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
14  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
15
16
17**/
18
19#include "Ebl.h"
20
21// Globals for command history processing
22INTN mCmdHistoryEnd     = -1;
23INTN mCmdHistoryStart   = -1;
24INTN mCmdHistoryCurrent = -1;
25CHAR8 mCmdHistory[MAX_CMD_HISTORY][MAX_CMD_LINE];
26CHAR8 *mCmdBlank = "";
27
28// Globals to remember current screen geometry
29UINTN gScreenColumns;
30UINTN gScreenRows;
31
32// Global to turn on/off breaking commands with prompts before they scroll the screen
33BOOLEAN gPageBreak = TRUE;
34
35VOID
36RingBufferIncrement (
37  IN  INTN  *Value
38  )
39{
40  *Value = *Value + 1;
41
42  if (*Value >= MAX_CMD_HISTORY) {
43    *Value = 0;
44  }
45}
46
47VOID
48RingBufferDecrement (
49  IN  INTN  *Value
50  )
51{
52  *Value = *Value - 1;
53
54  if (*Value < 0) {
55    *Value = MAX_CMD_HISTORY - 1;
56  }
57}
58
59/**
60  Save this command in the circular history buffer. Older commands are
61  overwritten with newer commands.
62
63  @param  Cmd   Command line to archive the history of.
64
65  @return None
66
67**/
68VOID
69SetCmdHistory (
70  IN  CHAR8 *Cmd
71  )
72{
73  // Don't bother adding empty commands to the list
74  if (AsciiStrLen(Cmd) != 0) {
75
76    // First entry
77    if (mCmdHistoryStart == -1) {
78      mCmdHistoryStart   = 0;
79      mCmdHistoryEnd     = 0;
80    } else {
81      // Record the new command at the next index
82      RingBufferIncrement(&mCmdHistoryStart);
83
84      // If the next index runs into the end index, shuffle end back by one
85      if (mCmdHistoryStart == mCmdHistoryEnd) {
86        RingBufferIncrement(&mCmdHistoryEnd);
87      }
88    }
89
90    // Copy the new command line into the ring buffer
91    AsciiStrnCpy(&mCmdHistory[mCmdHistoryStart][0], Cmd, MAX_CMD_LINE);
92  }
93
94  // Reset the command history for the next up arrow press
95  mCmdHistoryCurrent = mCmdHistoryStart;
96}
97
98
99/**
100  Retreave data from the Command History buffer. Direction maps into up arrow
101  an down arrow on the command line
102
103  @param  Direction  Command forward or back
104
105  @return The Command history based on the Direction
106
107**/
108CHAR8 *
109GetCmdHistory (
110  IN UINT16   Direction
111  )
112{
113  CHAR8 *HistoricalCommand = NULL;
114
115  // No history yet?
116  if (mCmdHistoryCurrent == -1) {
117    HistoricalCommand = mCmdBlank;
118    goto Exit;
119  }
120
121  if (Direction == SCAN_UP) {
122    HistoricalCommand = &mCmdHistory[mCmdHistoryCurrent][0];
123
124    // if we just echoed the last command, hang out there, don't wrap around
125    if (mCmdHistoryCurrent == mCmdHistoryEnd) {
126      goto Exit;
127    }
128
129    // otherwise, back up by one
130    RingBufferDecrement(&mCmdHistoryCurrent);
131
132  } else if (Direction == SCAN_DOWN) {
133
134    // if we last echoed the start command, put a blank prompt out
135    if (mCmdHistoryCurrent == mCmdHistoryStart) {
136      HistoricalCommand = mCmdBlank;
137      goto Exit;
138    }
139
140    // otherwise increment the current pointer and return that command
141    RingBufferIncrement(&mCmdHistoryCurrent);
142    RingBufferIncrement(&mCmdHistoryCurrent);
143
144    HistoricalCommand = &mCmdHistory[mCmdHistoryCurrent][0];
145    RingBufferDecrement(&mCmdHistoryCurrent);
146  }
147
148Exit:
149  return HistoricalCommand;
150}
151
152
153/**
154  Parse the CmdLine and break it up into Argc (arg count) and Argv (array of
155  pointers to each argument). The Cmd buffer is altered and separators are
156  converted to string terminators. This allows Argv to point into CmdLine.
157  A CmdLine can support multiple commands. The next command in the command line
158  is returned if it exists.
159
160  @param  CmdLine String to parse for a set of commands
161  @param  Argc    Returns the number of arguments in the CmdLine current command
162  @param  Argv    Argc pointers to each string in CmdLine
163
164  @return Next Command in the command line or NULL if non exists
165**/
166CHAR8 *
167ParseArguments (
168  IN  CHAR8  *CmdLine,
169  OUT UINTN  *Argc,
170  OUT CHAR8  **Argv
171  )
172{
173  UINTN   Arg;
174  CHAR8   *Char;
175  BOOLEAN LookingForArg;
176  BOOLEAN InQuote;
177
178  *Argc = 0;
179  if (AsciiStrLen (CmdLine) == 0) {
180    return NULL;
181  }
182
183  // Walk a single command line. A CMD_SEPARATOR allows multiple commands on a single line
184  InQuote       = FALSE;
185  LookingForArg = TRUE;
186  for (Char = CmdLine, Arg = 0; *Char != '\0'; Char++) {
187    if (!InQuote && *Char == CMD_SEPARATOR) {
188      break;
189    }
190
191    // Perform any text conversion here
192    if (*Char == '\t') {
193      // TAB to space
194      *Char = ' ';
195    }
196
197    if (LookingForArg) {
198      // Look for the beginning of an Argv[] entry
199      if (*Char == '"') {
200        Argv[Arg++] = ++Char;
201        LookingForArg = FALSE;
202        InQuote = TRUE;
203      } else if (*Char != ' ') {
204        Argv[Arg++] = Char;
205        LookingForArg = FALSE;
206      }
207    } else {
208      // Looking for the terminator of an Argv[] entry
209      if (!InQuote && (*Char == ' ')) {
210        *Char = '\0';
211        LookingForArg = TRUE;
212      } else if (!InQuote && (*Char == '"') && (*(Char-1) != '\\')) {
213        InQuote = TRUE;
214      } else if (InQuote && (*Char == '"') && (*(Char-1) != '\\')) {
215        *Char = '\0';
216        InQuote = FALSE;
217      }
218    }
219  }
220
221  *Argc = Arg;
222
223  if (*Char == CMD_SEPARATOR) {
224    // Replace the command delimiter with null and return pointer to next command line
225    *Char = '\0';
226    return ++Char;
227  }
228
229  return NULL;
230}
231
232
233/**
234  Return a keypress or optionally timeout if a timeout value was passed in.
235  An optional callback function is called every second when waiting for a
236  timeout.
237
238  @param  Key           EFI Key information returned
239  @param  TimeoutInSec  Number of seconds to wait to timeout
240  @param  CallBack      Callback called every second during the timeout wait
241
242  @return EFI_SUCCESS  Key was returned
243  @return EFI_TIMEOUT  If the TimoutInSec expired
244
245**/
246EFI_STATUS
247EFIAPI
248EblGetCharKey (
249  IN OUT EFI_INPUT_KEY            *Key,
250  IN     UINTN                    TimeoutInSec,
251  IN     EBL_GET_CHAR_CALL_BACK   CallBack   OPTIONAL
252  )
253{
254  EFI_STATUS    Status;
255  UINTN         WaitCount;
256  UINTN         WaitIndex;
257  EFI_EVENT     WaitList[2];
258
259  WaitCount   = 1;
260  WaitList[0] = gST->ConIn->WaitForKey;
261  if (TimeoutInSec != 0) {
262    // Create a time event for 1 sec duration if we have a timeout
263    gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &WaitList[1]);
264    gBS->SetTimer (WaitList[1], TimerPeriodic, EFI_SET_TIMER_TO_SECOND);
265    WaitCount++;
266  }
267
268  for (;;) {
269    Status = gBS->WaitForEvent (WaitCount, WaitList, &WaitIndex);
270    ASSERT_EFI_ERROR (Status);
271
272    switch (WaitIndex) {
273    case 0:
274      // Key event signaled
275      Status = gST->ConIn->ReadKeyStroke (gST->ConIn, Key);
276      if (!EFI_ERROR (Status)) {
277        if (WaitCount == 2) {
278          gBS->CloseEvent (WaitList[1]);
279        }
280        return EFI_SUCCESS;
281      }
282      break;
283
284    case 1:
285      // Periodic 1 sec timer signaled
286      TimeoutInSec--;
287      if (CallBack != NULL) {
288        // Call the users callback function if registered
289        CallBack (TimeoutInSec);
290      }
291      if (TimeoutInSec == 0) {
292        gBS->CloseEvent (WaitList[1]);
293        return EFI_TIMEOUT;
294      }
295      break;
296    default:
297      ASSERT (FALSE);
298    }
299  }
300}
301
302
303/**
304  This routine is used prevent command output data from scrolling off the end
305  of the screen. The global gPageBreak is used to turn on or off this feature.
306  If the CurrentRow is near the end of the screen pause and print out a prompt
307  If the use hits Q to quit return TRUE else for any other key return FALSE.
308  PrefixNewline is used to figure out if a newline is needed before the prompt
309  string. This depends on the last print done before calling this function.
310  CurrentRow is updated by one on a call or set back to zero if a prompt is
311  needed.
312
313  @param  CurrentRow  Used to figure out if its the end of the page and updated
314  @param  PrefixNewline  Did previous print issue a newline
315
316  @return TRUE if Q was hit to quit, FALSE in all other cases.
317
318**/
319BOOLEAN
320EFIAPI
321EblAnyKeyToContinueQtoQuit (
322  IN  UINTN   *CurrentRow,
323  IN  BOOLEAN PrefixNewline
324  )
325{
326  EFI_INPUT_KEY     InputKey;
327
328  if (!gPageBreak) {
329    // global disable for this feature
330    return FALSE;
331  }
332
333  if (*CurrentRow >= (gScreenRows - 2)) {
334    if (PrefixNewline) {
335      AsciiPrint ("\n");
336    }
337    AsciiPrint ("Any key to continue (Q to quit): ");
338    EblGetCharKey (&InputKey, 0, NULL);
339    AsciiPrint ("\n");
340
341    // Time to promt to stop the screen. We have to leave space for the prompt string
342    *CurrentRow = 0;
343    if (InputKey.UnicodeChar == 'Q' || InputKey.UnicodeChar == 'q') {
344      return TRUE;
345    }
346  } else {
347    *CurrentRow += 1;
348  }
349
350  return FALSE;
351}
352
353
354/**
355  Set the text color of the EFI Console. If a zero is passed in reset to
356  default text/background color.
357
358  @param  Attribute   For text and background color
359
360**/
361VOID
362EblSetTextColor (
363  UINTN   Attribute
364  )
365{
366  if (Attribute == 0) {
367    // Set the text color back to default
368    Attribute = (UINTN)PcdGet32 (PcdEmbeddedDefaultTextColor);
369  }
370
371  gST->ConOut->SetAttribute (gST->ConOut, Attribute);
372}
373
374
375/**
376  Collect the keyboard input for a cmd line. Carriage Return, New Line, or ESC
377  terminates the command line. You can edit the command line via left arrow,
378  delete and backspace and they all back up and erase the command line.
379  No edit of command line is possible without deletion at this time!
380  The up arrow and down arrow fill Cmd with information from the history
381  buffer.
382
383  @param  Cmd         Command line to return
384  @param  CmdMaxSize  Maximum size of Cmd
385
386  @return The Status of EblGetCharKey()
387
388**/
389EFI_STATUS
390GetCmd (
391  IN OUT  CHAR8   *Cmd,
392  IN      UINTN   CmdMaxSize
393  )
394{
395  EFI_STATUS    Status;
396  UINTN         Index;
397  UINTN         Index2;
398  CHAR8         Char;
399  CHAR8         *History;
400  EFI_INPUT_KEY Key;
401
402  for (Index = 0; Index < CmdMaxSize - 1;) {
403    Status = EblGetCharKey (&Key, 0, NULL);
404    if (EFI_ERROR (Status)) {
405      Cmd[Index] = '\0';
406      AsciiPrint ("\n");
407      return Status;
408    }
409
410    Char = (CHAR8)Key.UnicodeChar;
411    if ((Char == '\n') || (Char == '\r') || (Char == 0x7f)) {
412      Cmd[Index] = '\0';
413      if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho) == TRUE) {
414        AsciiPrint ("\n\r");
415      }
416      return EFI_SUCCESS;
417    } else if ((Char == '\b') || (Key.ScanCode == SCAN_LEFT) || (Key.ScanCode == SCAN_DELETE)){
418      if (Index != 0) {
419        Index--;
420        //
421        // Update the display
422        //
423        AsciiPrint ("\b \b");
424      }
425    } else if ((Key.ScanCode == SCAN_UP) || Key.ScanCode == SCAN_DOWN) {
426      History = GetCmdHistory (Key.ScanCode);
427      //
428      // Clear display line
429      //
430      for (Index2 = 0; Index2 < Index; Index2++) {
431        AsciiPrint ("\b \b");
432      }
433      AsciiPrint (History);
434      Index = AsciiStrLen (History);
435      AsciiStrnCpy (Cmd, History, CmdMaxSize);
436    } else {
437      Cmd[Index++] = Char;
438      if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho) == TRUE) {
439        AsciiPrint ("%c", Char);
440      }
441    }
442  }
443
444  return EFI_SUCCESS;
445}
446
447
448/**
449  Print the boot up banner for the EBL.
450**/
451VOID
452EblPrintStartupBanner (
453  VOID
454  )
455{
456  AsciiPrint ("Embedded Boot Loader (");
457  EblSetTextColor (EFI_YELLOW);
458  AsciiPrint ("EBL");
459  EblSetTextColor (0);
460  AsciiPrint (") prototype. Built at %a on %a\n",__TIME__, __DATE__);
461  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");
462  AsciiPrint ("Please send feedback to edk2-devel@lists.sourceforge.net\n");
463}
464
465
466/**
467  Send null requests to all removable media block IO devices so the a media add/remove/change
468  can be detected in real before we execute a command.
469
470  This is mainly due to the fact that the FAT driver does not do this today so you can get stale
471  dir commands after an SD Card has been removed.
472**/
473VOID
474EblProbeRemovableMedia (
475  VOID
476  )
477{
478  UINTN         Index;
479  UINTN         Max;
480  EFI_OPEN_FILE *File;
481
482  //
483  // Probe for media insertion/removal in removable media devices
484  //
485  Max = EfiGetDeviceCounts (EfiOpenBlockIo);
486  if (Max != 0) {
487    for (Index = 0; Index < Max; Index++) {
488      File = EfiDeviceOpenByType (EfiOpenBlockIo, Index);
489      if (File != NULL) {
490        if (File->FsBlockIoMedia->RemovableMedia) {
491          // Probe to see if media is present (or not) or media changed
492          //  this causes the ReinstallProtocolInterface() to fire in the
493          //  block io driver to update the system about media change events
494          File->FsBlockIo->ReadBlocks (File->FsBlockIo, File->FsBlockIo->Media->MediaId, (EFI_LBA)0, 0, NULL);
495        }
496        EfiClose (File);
497      }
498    }
499  }
500}
501
502
503
504
505/**
506  Print the prompt for the EBL.
507**/
508VOID
509EblPrompt (
510  VOID
511  )
512{
513  EblSetTextColor (EFI_YELLOW);
514  AsciiPrint ("%a %a",(CHAR8 *)PcdGetPtr (PcdEmbeddedPrompt), EfiGetCwd ());
515  EblSetTextColor (0);
516  AsciiPrint ("%a", ">");
517}
518
519
520
521/**
522  Parse a command line and execute the commands. The ; separator allows
523  multiple commands for each command line. Stop processing if one of the
524  commands returns an error.
525
526  @param  CmdLine          Command Line to process.
527  @param  MaxCmdLineSize   MaxSize of the Command line
528
529  @return EFI status of the Command
530
531**/
532EFI_STATUS
533ProcessCmdLine (
534  IN CHAR8      *CmdLine,
535  IN UINTN      MaxCmdLineSize
536  )
537{
538  EFI_STATUS          Status;
539  EBL_COMMAND_TABLE   *Cmd;
540  CHAR8               *Ptr;
541  UINTN               Argc;
542  CHAR8               *Argv[MAX_ARGS];
543
544  // Parse the command line. The loop processes commands separated by ;
545  for (Ptr = CmdLine, Status = EFI_SUCCESS; Ptr != NULL;) {
546    Ptr = ParseArguments (Ptr, &Argc, Argv);
547    if (Argc != 0) {
548      Cmd = EblGetCommand (Argv[0]);
549      if (Cmd != NULL) {
550        // Execute the Command!
551        Status = Cmd->Command (Argc, Argv);
552        if (Status == EFI_ABORTED) {
553          // exit command so lets exit
554          break;
555        } else if (Status == EFI_TIMEOUT) {
556          // pause command got input so don't process any more cmd on this cmd line
557          break;
558        } else if (EFI_ERROR (Status)) {
559          AsciiPrint ("%a returned %r error\n", Cmd->Name, Status);
560          // if any command fails stop processing CmdLine
561          break;
562        }
563      } else {
564        AsciiPrint ("The command '%a' is not supported.\n", Argv[0]);
565      }
566    }
567  }
568
569  return Status;
570}
571
572
573
574/**
575  Embedded Boot Loader (EBL) - A simple EFI command line application for embedded
576  devices. PcdEmbeddedAutomaticBootCommand is a complied in command line that
577  gets executed automatically. The ; separator allows multiple commands
578  for each command line.
579
580  @param  ImageHandle   EFI ImageHandle for this application.
581  @param  SystemTable   EFI system table
582
583  @return EFI status of the application
584
585**/
586EFI_STATUS
587EFIAPI
588EdkBootLoaderEntry (
589  IN EFI_HANDLE                            ImageHandle,
590  IN EFI_SYSTEM_TABLE                      *SystemTable
591  )
592{
593  EFI_STATUS  Status;
594  CHAR8       CmdLine[MAX_CMD_LINE];
595  CHAR16      *CommandLineVariable = NULL;
596  CHAR16      *CommandLineVariableName = L"default-cmdline";
597  UINTN       CommandLineVariableSize = 0;
598  EFI_GUID    VendorGuid;
599
600  // Initialize tables of commands
601  EblInitializeCmdTable ();
602  EblInitializeDeviceCmd ();
603  EblInitializemdHwDebugCmds ();
604  EblInitializemdHwIoDebugCmds ();
605  EblInitializeDirCmd ();
606  EblInitializeHobCmd ();
607  EblInitializeScriptCmd ();
608  EblInitializeExternalCmd ();
609  EblInitializeNetworkCmd();
610  EblInitializeVariableCmds ();
611
612  if (gST->ConOut == NULL) {
613    DEBUG((EFI_D_ERROR,"Error: No Console Output\n"));
614    return EFI_NOT_READY;
615  }
616
617  // Disable the 5 minute EFI watchdog time so we don't get automatically reset
618  gBS->SetWatchdogTimer (0, 0, 0, NULL);
619
620  if (FeaturePcdGet (PcdEmbeddedMacBoot)) {
621    // A MAC will boot in graphics mode, so turn it back to text here
622    // This protocol was removed from edk2. It is only an edk thing. We need to make our own copy.
623    // DisableQuietBoot ();
624
625    // Enable the biggest output screen size possible
626    gST->ConOut->SetMode (gST->ConOut, (UINTN)gST->ConOut->Mode->MaxMode - 1);
627
628  }
629
630  // Save current screen mode
631  gST->ConOut->QueryMode (gST->ConOut, gST->ConOut->Mode->Mode, &gScreenColumns, &gScreenRows);
632
633  EblPrintStartupBanner ();
634
635  // Parse command line and handle commands separated by ;
636  // The loop prints the prompt gets user input and saves history
637
638  // Look for a variable with a default command line, otherwise use the Pcd
639  ZeroMem(&VendorGuid, sizeof(EFI_GUID));
640
641  Status = gRT->GetVariable(CommandLineVariableName, &VendorGuid, NULL, &CommandLineVariableSize, CommandLineVariable);
642  if (Status == EFI_BUFFER_TOO_SMALL) {
643    CommandLineVariable = AllocatePool(CommandLineVariableSize);
644
645    Status = gRT->GetVariable(CommandLineVariableName, &VendorGuid, NULL, &CommandLineVariableSize, CommandLineVariable);
646    if (!EFI_ERROR(Status)) {
647      UnicodeStrToAsciiStr(CommandLineVariable, CmdLine);
648    }
649
650    FreePool(CommandLineVariable);
651  }
652
653  if (EFI_ERROR(Status)) {
654    AsciiStrCpy (CmdLine, (CHAR8 *)PcdGetPtr (PcdEmbeddedAutomaticBootCommand));
655  }
656
657  for (;;) {
658    Status = ProcessCmdLine (CmdLine, MAX_CMD_LINE);
659    if (Status == EFI_ABORTED) {
660      // if a command returns EFI_ABORTED then exit the EBL
661      EblShutdownExternalCmdTable ();
662      return EFI_SUCCESS;
663    }
664
665    // get the command line from the user
666    EblPrompt ();
667    GetCmd (CmdLine, MAX_CMD_LINE);
668    SetCmdHistory (CmdLine);
669
670    if (FeaturePcdGet (PcdEmbeddedProbeRemovable)) {
671      // Probe removable media devices to see if media has been inserted or removed.
672      EblProbeRemovableMedia ();
673    }
674  }
675}
676
677
678