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