]> git.proxmox.com Git - mirror_edk2.git/blob - EmbeddedPkg/Ebl/Main.c
Added a PCD to turn on/off probing Block IO devices to detect add/remove/change....
[mirror_edk2.git] / EmbeddedPkg / Ebl / Main.c
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
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
21 INTN mCmdHistoryEnd = -1;
22 INTN mCmdHistoryStart = -1;
23 INTN mCmdHistoryCurrent = -1;
24 CHAR8 mCmdHistory[MAX_CMD_HISTORY][MAX_CMD_LINE];
25 CHAR8 *mCmdBlank = "";
26
27 // Globals to remember current screen geometry
28 UINTN gScreenColumns;
29 UINTN gScreenRows;
30
31 // Global to turn on/off breaking commands with prompts before they scroll the screen
32 BOOLEAN gPageBreak = TRUE;
33
34 VOID
35 RingBufferIncrement (
36 IN INTN *Value
37 )
38 {
39 *Value = *Value + 1;
40
41 if (*Value >= MAX_CMD_HISTORY) {
42 *Value = 0;
43 }
44 }
45
46 VOID
47 RingBufferDecrement (
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 **/
67 VOID
68 SetCmdHistory (
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 **/
107 CHAR8 *
108 GetCmdHistory (
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
147 Exit:
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 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.
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 **/
165 CHAR8 *
166 ParseArguments (
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_SEPERATOR allows mult 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_SEPERATOR) {
187 break;
188 }
189
190 // Perform any text coversion here
191 if (*Char == '\t') {
192 // TAB to space
193 *Char = ' ';
194 }
195
196 if (LookingForArg) {
197 // Look for the beging 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 == '"')) || (!InQuote && (*Char == ' '))) {
209 *Char = '\0';
210 LookingForArg = TRUE;
211 }
212 }
213 }
214
215 *Argc = Arg;
216
217 if (*Char == CMD_SEPERATOR) {
218 // Replace the command delimeter with null and return pointer to next command line
219 *Char = '\0';
220 return ++Char;
221 }
222
223 return NULL;
224 }
225
226
227 /**
228 Return a keypress or optionally timeout if a timeout value was passed in.
229 An optional callback funciton is called evey second when waiting for a
230 timeout.
231
232 @param Key EFI Key information returned
233 @param TimeoutInSec Number of seconds to wait to timeout
234 @param CallBack Callback called every second during the timeout wait
235
236 @return EFI_SUCCESS Key was returned
237 @return EFI_TIMEOUT If the TimoutInSec expired
238
239 **/
240 EFI_STATUS
241 EblGetCharKey (
242 IN OUT EFI_INPUT_KEY *Key,
243 IN UINTN TimeoutInSec,
244 IN EBL_GET_CHAR_CALL_BACK CallBack OPTIONAL
245 )
246 {
247 EFI_STATUS Status;
248 UINTN WaitCount;
249 UINTN WaitIndex;
250 EFI_EVENT WaitList[2];
251
252 WaitCount = 1;
253 WaitList[0] = gST->ConIn->WaitForKey;
254 if (TimeoutInSec != 0) {
255 // Create a time event for 1 sec duration if we have a timeout
256 gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &WaitList[1]);
257 gBS->SetTimer (WaitList[1], TimerPeriodic, EFI_SET_TIMER_TO_SECOND);
258 WaitCount++;
259 }
260
261 for (;;) {
262 Status = gBS->WaitForEvent (WaitCount, WaitList, &WaitIndex);
263 ASSERT_EFI_ERROR (Status);
264
265 switch (WaitIndex) {
266 case 0:
267 // Key event signaled
268 Status = gST->ConIn->ReadKeyStroke (gST->ConIn, Key);
269 if (!EFI_ERROR (Status)) {
270 if (WaitCount == 2) {
271 gBS->CloseEvent (WaitList[1]);
272 }
273 return EFI_SUCCESS;
274 }
275 break;
276
277 case 1:
278 // Periodic 1 sec timer signaled
279 TimeoutInSec--;
280 if (CallBack != NULL) {
281 // Call the users callback function if registered
282 CallBack (TimeoutInSec);
283 }
284 if (TimeoutInSec == 0) {
285 gBS->CloseEvent (WaitList[1]);
286 return EFI_TIMEOUT;
287 }
288 break;
289 default:
290 ASSERT (FALSE);
291 }
292 }
293 }
294
295
296 /**
297 This routine is used prevent command output data from scrolling off the end
298 of the screen. The global gPageBreak is used to turn on or off this feature.
299 If the CurrentRow is near the end of the screen pause and print out a prompt
300 If the use hits Q to quit return TRUE else for any other key return FALSE.
301 PrefixNewline is used to figure out if a newline is needed before the prompt
302 string. This depends on the last print done before calling this function.
303 CurrentRow is updated by one on a call or set back to zero if a prompt is
304 needed.
305
306 @param CurrentRow Used to figure out if its the end of the page and updated
307 @param PrefixNewline Did previous print issue a newline
308
309 @return TRUE if Q was hit to quit, FALSE in all other cases.
310
311 **/
312 BOOLEAN
313 EblAnyKeyToContinueQtoQuit (
314 IN UINTN *CurrentRow,
315 IN BOOLEAN PrefixNewline
316 )
317 {
318 EFI_INPUT_KEY InputKey;
319
320 if (!gPageBreak) {
321 // global disable for this feature
322 return FALSE;
323 }
324
325 if (*CurrentRow >= (gScreenRows - 2)) {
326 if (PrefixNewline) {
327 AsciiPrint ("\n");
328 }
329 AsciiPrint ("Any key to continue (Q to quit): ");
330 EblGetCharKey (&InputKey, 0, NULL);
331 AsciiPrint ("\n");
332
333 // Time to promt to stop the screen. We have to leave space for the prompt string
334 *CurrentRow = 0;
335 if (InputKey.UnicodeChar == 'Q' || InputKey.UnicodeChar == 'q') {
336 return TRUE;
337 }
338 } else {
339 *CurrentRow += 1;
340 }
341
342 return FALSE;
343 }
344
345
346 /**
347 Set the text color of the EFI Console. If a zero is passed in reset to
348 default text/background color.
349
350 @param Attribute For text and background color
351
352 **/
353 VOID
354 EblSetTextColor (
355 UINTN Attribute
356 )
357 {
358 if (Attribute == 0) {
359 // Set the text color back to default
360 Attribute = (UINTN)PcdGet32 (PcdEmbeddedDefaultTextColor);
361 }
362
363 gST->ConOut->SetAttribute (gST->ConOut, Attribute);
364 }
365
366
367 /**
368 Collect the keyboard input for a cmd line. Carage Return, New Line, or ESC
369 terminates the command line. You can edit the command line via left arrow,
370 delete and backspace and they all back up and erase the command line.
371 No edit of commnad line is possible without deletion at this time!
372 The up arrow and down arrow fill Cmd with information from the history
373 buffer.
374
375 @param Cmd Command line to return
376 @param CmdMaxSize Maximum size of Cmd
377
378 @return The Status of EblGetCharKey()
379
380 **/
381 EFI_STATUS
382 GetCmd (
383 IN OUT CHAR8 *Cmd,
384 IN UINTN CmdMaxSize
385 )
386 {
387 EFI_STATUS Status;
388 UINTN Index;
389 UINTN Index2;
390 CHAR8 Char;
391 CHAR8 *History;
392 EFI_INPUT_KEY Key;
393
394 for (Index = 0; Index < CmdMaxSize - 1;) {
395 Status = EblGetCharKey (&Key, 0, NULL);
396 if (EFI_ERROR (Status)) {
397 Cmd[Index] = '\0';
398 AsciiPrint ("\n");
399 return Status;
400 }
401
402 Char = (CHAR8)Key.UnicodeChar;
403 if ((Char == '\n') || (Char == '\r') || (Char == 0x7f)) {
404 Cmd[Index] = '\0';
405 if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho) == TRUE) {
406 AsciiPrint ("\n\r");
407 }
408 return EFI_SUCCESS;
409 } else if ((Char == '\b') || (Key.ScanCode == SCAN_LEFT) || (Key.ScanCode == SCAN_DELETE)){
410 if (Index != 0) {
411 Index--;
412 //
413 // Update the display
414 //
415 AsciiPrint ("\b \b");
416 }
417 } else if ((Key.ScanCode == SCAN_UP) || Key.ScanCode == SCAN_DOWN) {
418 History = GetCmdHistory (Key.ScanCode);
419 //
420 // Clear display line
421 //
422 for (Index2 = 0; Index2 < Index; Index2++) {
423 AsciiPrint ("\b \b");
424 }
425 AsciiPrint (History);
426 Index = AsciiStrLen (History);
427 AsciiStrnCpy (Cmd, History, CmdMaxSize);
428 } else {
429 Cmd[Index++] = Char;
430 if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho) == TRUE) {
431 AsciiPrint ("%c", Char);
432 }
433 }
434 }
435
436 return EFI_SUCCESS;
437 }
438
439
440 /**
441 Print the boot up banner for the EBL.
442 **/
443 VOID
444 EblPrintStartupBanner (
445 VOID
446 )
447 {
448 AsciiPrint ("Embedded Boot Loader (");
449 EblSetTextColor (EFI_YELLOW);
450 AsciiPrint ("EBL");
451 EblSetTextColor (0);
452 AsciiPrint (") prototype. Built at %a on %a\n",__TIME__, __DATE__);
453 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");
454 AsciiPrint ("Please send feedback to edk2-devel@lists.sourceforge.net\n");
455 }
456
457
458 /**
459 Send null requests to all removable media block IO devices so the a media add/remove/change
460 can be detected in real before we execute a command.
461
462 This is mainly due to the fact that the FAT driver does not do this today so you can get stale
463 dir commands after an SD Card has been removed.
464 **/
465 VOID
466 EblProbeRemovableMedia (
467 VOID
468 )
469 {
470 UINTN Index;
471 UINTN Max;
472 EFI_OPEN_FILE *File;
473
474 //
475 // Probe for media insertion/removal in removable media devices
476 //
477 Max = EfiGetDeviceCounts (EfiOpenBlockIo);
478 if (Max != 0) {
479 for (Index = 0; Index < Max; Index++) {
480 File = EfiDeviceOpenByType (EfiOpenBlockIo, Index);
481 if (File != NULL) {
482 if (File->FsBlockIoMedia->RemovableMedia) {
483 // Probe to see if media is present (or not) or media changed
484 // this causes the ReinstallProtocolInterface() to fire in the
485 // block io driver to update the system about media change events
486 File->FsBlockIo->ReadBlocks (File->FsBlockIo, File->FsBlockIo->Media->MediaId, (EFI_LBA)0, 0, NULL);
487 }
488 EfiClose (File);
489 }
490 }
491 }
492 }
493
494
495
496
497 /**
498 Print the prompt for the EBL.
499 **/
500 VOID
501 EblPrompt (
502 VOID
503 )
504 {
505 EblSetTextColor (EFI_YELLOW);
506 AsciiPrint ((CHAR8 *)PcdGetPtr (PcdEmbeddedPrompt), EfiGetCwd ());
507 EblSetTextColor (0);
508 AsciiPrint ("%a", ">");
509 }
510
511
512
513 /**
514 Parse a command line and execute the commands. The ; seperator allows
515 multiple commands for each command line. Stop processing if one of the
516 commands returns an error.
517
518 @param CmdLine Command Line to process.
519 @param MaxCmdLineSize MaxSize of the Command line
520
521 @return EFI status of the Command
522
523 **/
524 EFI_STATUS
525 ProcessCmdLine (
526 IN CHAR8 *CmdLine,
527 IN UINTN MaxCmdLineSize
528 )
529 {
530 EFI_STATUS Status;
531 EBL_COMMAND_TABLE *Cmd;
532 CHAR8 *Ptr;
533 UINTN Argc;
534 CHAR8 *Argv[MAX_ARGS];
535
536 // Parse the command line. The loop processes commands seperated by ;
537 for (Ptr = CmdLine, Status = EFI_SUCCESS; Ptr != NULL;) {
538 Ptr = ParseArguments (Ptr, &Argc, Argv);
539 if (Argc != 0) {
540 Cmd = EblGetCommand (Argv[0]);
541 if (Cmd != NULL) {
542 // Execute the Command!
543 Status = Cmd->Command (Argc, Argv);
544 if (Status == EFI_ABORTED) {
545 // exit command so lets exit
546 break;
547 } else if (Status == EFI_TIMEOUT) {
548 // pause command got imput so don't process any more cmd on this cmd line
549 break;
550 } else if (EFI_ERROR (Status)) {
551 AsciiPrint ("%a returned %r error\n", Cmd->Name, Status);
552 // if any command fails stop processing CmdLine
553 break;
554 }
555 }
556 }
557 }
558
559 return Status;
560 }
561
562
563
564 /**
565 Embedded Boot Loader (EBL) - A simple EFI command line application for embedded
566 devices. PcdEmbeddedAutomaticBootCommand is a complied in commnad line that
567 gets executed automatically. The ; seperator allows multiple commands
568 for each command line.
569
570 @param ImageHandle EFI ImageHandle for this application.
571 @param SystemTable EFI system table
572
573 @return EFI status of the applicaiton
574
575 **/
576 EFI_STATUS
577 EFIAPI
578 EdkBootLoaderEntry (
579 IN EFI_HANDLE ImageHandle,
580 IN EFI_SYSTEM_TABLE *SystemTable
581 )
582 {
583 EFI_STATUS Status;
584 CHAR8 CmdLine[MAX_CMD_LINE];
585 CHAR16 *CommandLineVariable = NULL;
586 CHAR16 *CommandLineVariableName = L"default-cmdline";
587 UINTN CommandLineVariableSize = 0;
588 EFI_GUID VendorGuid;
589
590 // Initialize tables of commnads
591 EblInitializeCmdTable ();
592 EblInitializeDeviceCmd ();
593 EblInitializemdHwDebugCmds ();
594 EblInitializemdHwIoDebugCmds ();
595 EblInitializeDirCmd ();
596 EblInitializeHobCmd ();
597 EblInitializeScriptCmd ();
598 EblInitializeExternalCmd ();
599 EblInitializeNetworkCmd();
600
601 // Disable the 5 minute EFI watchdog time so we don't get automatically reset
602 gBS->SetWatchdogTimer (0, 0, 0, NULL);
603
604 if (FeaturePcdGet (PcdEmbeddedMacBoot)) {
605 // A MAC will boot in graphics mode, so turn it back to text here
606 // This protocol was removed from edk2. It is only an edk thing. We need to make our own copy.
607 // DisableQuietBoot ();
608
609 // Enable the biggest output screen size possible
610 gST->ConOut->SetMode (gST->ConOut, (UINTN)gST->ConOut->Mode->MaxMode - 1);
611
612 }
613
614 // Save current screen mode
615 gST->ConOut->QueryMode (gST->ConOut, gST->ConOut->Mode->Mode, &gScreenColumns, &gScreenRows);
616
617 EblPrintStartupBanner ();
618
619 // Parse command line and handle commands seperated by ;
620 // The loop prints the prompt gets user input and saves history
621
622 // Look for a variable with a default command line, otherwise use the Pcd
623 ZeroMem(&VendorGuid, sizeof(EFI_GUID));
624
625 Status = gRT->GetVariable(CommandLineVariableName, &VendorGuid, NULL, &CommandLineVariableSize, CommandLineVariable);
626 if (Status == EFI_BUFFER_TOO_SMALL) {
627 CommandLineVariable = AllocatePool(CommandLineVariableSize);
628
629 Status = gRT->GetVariable(CommandLineVariableName, &VendorGuid, NULL, &CommandLineVariableSize, CommandLineVariable);
630 if (!EFI_ERROR(Status)) {
631 UnicodeStrToAsciiStr(CommandLineVariable, CmdLine);
632 }
633
634 FreePool(CommandLineVariable);
635 }
636
637 if (EFI_ERROR(Status)) {
638 AsciiStrCpy (CmdLine, (CHAR8 *)PcdGetPtr (PcdEmbeddedAutomaticBootCommand));
639 }
640
641 for (;;) {
642 Status = ProcessCmdLine (CmdLine, MAX_CMD_LINE);
643 if (Status == EFI_ABORTED) {
644 // if a command returns EFI_ABORTED then exit the EBL
645 EblShutdownExternalCmdTable ();
646 return EFI_SUCCESS;
647 }
648
649 // get the command line from the user
650 EblPrompt ();
651 GetCmd (CmdLine, MAX_CMD_LINE);
652 SetCmdHistory (CmdLine);
653
654 if (FeaturePcdGet (PcdEmbeddedProbeRemovable)) {
655 // Probe removable media devices to see if media has been inserted or removed.
656 EblProbeRemovableMedia ();
657 }
658 }
659 }
660
661