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 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 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 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 * 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 * 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 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 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 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 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 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 (at) 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 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 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 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 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