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
22 INTN mCmdHistoryEnd = -1;
23 INTN mCmdHistoryStart = -1;
24 INTN mCmdHistoryCurrent = -1;
25 CHAR8 mCmdHistory[MAX_CMD_HISTORY][MAX_CMD_LINE];
26 CHAR8 *mCmdBlank = "";
27
28 // Globals to remember current screen geometry
29 UINTN gScreenColumns;
30 UINTN gScreenRows;
31
32 // Global to turn on/off breaking commands with prompts before they scroll the screen
33 BOOLEAN gPageBreak = TRUE;
34
35 VOID
RingBufferIncrement(IN INTN * Value)36 RingBufferIncrement (
37 IN INTN *Value
38 )
39 {
40 *Value = *Value + 1;
41
42 if (*Value >= MAX_CMD_HISTORY) {
43 *Value = 0;
44 }
45 }
46
47 VOID
RingBufferDecrement(IN INTN * Value)48 RingBufferDecrement (
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 **/
68 VOID
SetCmdHistory(IN CHAR8 * Cmd)69 SetCmdHistory (
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 AsciiStrnCpyS (&mCmdHistory[mCmdHistoryStart][0], MAX_CMD_LINE, 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 **/
108 CHAR8 *
GetCmdHistory(IN UINT16 Direction)109 GetCmdHistory (
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
148 Exit:
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 **/
166 CHAR8 *
ParseArguments(IN CHAR8 * CmdLine,OUT UINTN * Argc,OUT CHAR8 ** Argv)167 ParseArguments (
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 **/
246 EFI_STATUS
247 EFIAPI
EblGetCharKey(IN OUT EFI_INPUT_KEY * Key,IN UINTN TimeoutInSec,IN EBL_GET_CHAR_CALL_BACK CallBack OPTIONAL)248 EblGetCharKey (
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 **/
319 BOOLEAN
320 EFIAPI
EblAnyKeyToContinueQtoQuit(IN UINTN * CurrentRow,IN BOOLEAN PrefixNewline)321 EblAnyKeyToContinueQtoQuit (
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 **/
361 VOID
EblSetTextColor(UINTN Attribute)362 EblSetTextColor (
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 **/
389 EFI_STATUS
GetCmd(IN OUT CHAR8 * Cmd,IN UINTN CmdMaxSize)390 GetCmd (
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 AsciiStrnCpyS (Cmd, CmdMaxSize, 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 **/
451 VOID
EblPrintStartupBanner(VOID)452 EblPrintStartupBanner (
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 **/
473 VOID
EblProbeRemovableMedia(VOID)474 EblProbeRemovableMedia (
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 **/
508 VOID
EblPrompt(VOID)509 EblPrompt (
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 **/
532 EFI_STATUS
ProcessCmdLine(IN CHAR8 * CmdLine,IN UINTN MaxCmdLineSize)533 ProcessCmdLine (
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 **/
586 EFI_STATUS
587 EFIAPI
EdkBootLoaderEntry(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)588 EdkBootLoaderEntry (
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 UnicodeStrToAsciiStrS (CommandLineVariable, CmdLine, MAX_CMD_LINE);
648 }
649
650 FreePool(CommandLineVariable);
651 }
652
653 if (EFI_ERROR(Status)) {
654 AsciiStrCpyS (CmdLine, MAX_CMD_LINE, (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