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