/** @file | |
Simple Console that sits on a SerialLib. | |
Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
/* | |
Symbols used in table below | |
=========================== | |
ESC = 0x1B | |
CSI = 0x9B | |
DEL = 0x7f | |
^ = CTRL | |
+=========+======+===========+==========+==========+ | |
| | EFI | UEFI 2.0 | | | | |
| | Scan | | VT100+ | | | |
| KEY | Code | PC ANSI | VTUTF8 | VT100 | | |
+=========+======+===========+==========+==========+ | |
| NULL | 0x00 | | | | | |
| UP | 0x01 | ESC [ A | ESC [ A | ESC [ A | | |
| DOWN | 0x02 | ESC [ B | ESC [ B | ESC [ B | | |
| RIGHT | 0x03 | ESC [ C | ESC [ C | ESC [ C | | |
| LEFT | 0x04 | ESC [ D | ESC [ D | ESC [ D | | |
| HOME | 0x05 | ESC [ H | ESC h | ESC [ H | | |
| END | 0x06 | ESC [ F | ESC k | ESC [ K | | |
| INSERT | 0x07 | ESC [ @ | ESC + | ESC [ @ | | |
| | | ESC [ L | | ESC [ L | | |
| DELETE | 0x08 | ESC [ X | ESC - | ESC [ P | | |
| PG UP | 0x09 | ESC [ I | ESC ? | ESC [ V | | |
| | | | | ESC [ ? | | |
| PG DOWN | 0x0A | ESC [ G | ESC / | ESC [ U | | |
| | | | | ESC [ / | | |
| F1 | 0x0B | ESC [ M | ESC 1 | ESC O P | | |
| F2 | 0x0C | ESC [ N | ESC 2 | ESC O Q | | |
| F3 | 0x0D | ESC [ O | ESC 3 | ESC O w | | |
| F4 | 0x0E | ESC [ P | ESC 4 | ESC O x | | |
| F5 | 0x0F | ESC [ Q | ESC 5 | ESC O t | | |
| F6 | 0x10 | ESC [ R | ESC 6 | ESC O u | | |
| F7 | 0x11 | ESC [ S | ESC 7 | ESC O q | | |
| F8 | 0x12 | ESC [ T | ESC 8 | ESC O r | | |
| F9 | 0x13 | ESC [ U | ESC 9 | ESC O p | | |
| F10 | 0x14 | ESC [ V | ESC 0 | ESC O M | | |
| Escape | 0x17 | ESC | ESC | ESC | | |
| F11 | 0x15 | | ESC ! | | | |
| F12 | 0x16 | | ESC @ | | | |
+=========+======+===========+==========+==========+ | |
*/ | |
#include <PiDxe.h> | |
#include <Library/UefiLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/BaseLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/SerialPortLib.h> | |
#include <Library/PcdLib.h> | |
#include <Protocol/SerialIo.h> | |
#include <Protocol/SimpleTextIn.h> | |
#include <Protocol/SimpleTextOut.h> | |
#include <Protocol/DevicePath.h> | |
#define MODE0_COLUMN_COUNT 80 | |
#define MODE0_ROW_COUNT 25 | |
EFI_STATUS | |
EFIAPI | |
TextInReset ( | |
IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This, | |
IN BOOLEAN ExtendedVerification | |
); | |
EFI_STATUS | |
EFIAPI | |
ReadKeyStroke ( | |
IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This, | |
OUT EFI_INPUT_KEY *Key | |
); | |
EFI_STATUS | |
EFIAPI | |
TextOutReset ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN BOOLEAN ExtendedVerification | |
); | |
CHAR8 * | |
EFIAPI | |
SafeUnicodeStrToAsciiStr ( | |
IN CONST CHAR16 *Source, | |
OUT CHAR8 *Destination | |
); | |
EFI_STATUS | |
EFIAPI | |
OutputString ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN CHAR16 *String | |
); | |
EFI_STATUS | |
EFIAPI | |
TestString ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN CHAR16 *String | |
); | |
EFI_STATUS | |
EFIAPI | |
QueryMode ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN UINTN ModeNumber, | |
OUT UINTN *Columns, | |
OUT UINTN *Rows | |
); | |
EFI_STATUS | |
EFIAPI | |
SetMode ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN UINTN ModeNumber | |
); | |
EFI_STATUS | |
EFIAPI | |
SetAttribute ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN UINTN Attribute | |
); | |
EFI_STATUS | |
EFIAPI | |
ClearScreen ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This | |
); | |
EFI_STATUS | |
EFIAPI | |
SetCursorPosition ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN UINTN Column, | |
IN UINTN Row | |
); | |
EFI_STATUS | |
EFIAPI | |
EnableCursor ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN BOOLEAN Enable | |
); | |
EFI_SIMPLE_TEXT_INPUT_PROTOCOL mSimpleTextIn = { | |
TextInReset, | |
ReadKeyStroke, | |
NULL | |
}; | |
EFI_SIMPLE_TEXT_OUTPUT_MODE mSimpleTextOutMode = { | |
1, | |
0, | |
EFI_TEXT_ATTR (EFI_LIGHTGRAY,EFI_BLACK), | |
0, | |
0, | |
TRUE | |
}; | |
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL mSimpleTextOut = { | |
TextOutReset, | |
OutputString, | |
TestString, | |
QueryMode, | |
SetMode, | |
SetAttribute, | |
ClearScreen, | |
SetCursorPosition, | |
EnableCursor, | |
&mSimpleTextOutMode | |
}; | |
EFI_HANDLE mInstallHandle = NULL; | |
typedef struct { | |
VENDOR_DEVICE_PATH Guid; | |
UART_DEVICE_PATH Uart; | |
EFI_DEVICE_PATH_PROTOCOL End; | |
} SIMPLE_TEXT_OUT_DEVICE_PATH; | |
SIMPLE_TEXT_OUT_DEVICE_PATH mDevicePath = { | |
{ | |
{ HARDWARE_DEVICE_PATH, HW_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH), 0 } | |
}, | |
EFI_CALLER_ID_GUID | |
}, | |
{ | |
{ MESSAGING_DEVICE_PATH, MSG_UART_DP, { sizeof (UART_DEVICE_PATH), 0 } | |
}, | |
0, // Reserved | |
FixedPcdGet64 (PcdUartDefaultBaudRate), // BaudRate | |
FixedPcdGet8 (PcdUartDefaultDataBits), // DataBits | |
FixedPcdGet8 (PcdUartDefaultParity), // Parity (N) | |
FixedPcdGet8 (PcdUartDefaultStopBits) // StopBits | |
}, | |
{ END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 } | |
} | |
}; | |
BOOLEAN | |
TextOutIsValidAscii ( | |
IN CHAR16 Ascii | |
) | |
{ | |
// | |
// valid ASCII code lies in the extent of 0x20 - 0x7F | |
// | |
if ((Ascii >= 0x20) && (Ascii <= 0x7F)) { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
BOOLEAN | |
TextOutIsValidEfiCntlChar ( | |
IN CHAR16 Char | |
) | |
{ | |
// | |
// only support four control characters. | |
// | |
if ((Char == CHAR_NULL) || | |
(Char == CHAR_BACKSPACE) || | |
(Char == CHAR_LINEFEED) || | |
(Char == CHAR_CARRIAGE_RETURN) || | |
(Char == CHAR_TAB)) | |
{ | |
return TRUE; | |
} | |
return FALSE; | |
} | |
VOID | |
EFIAPI | |
WaitForKeyEvent ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
if (SerialPortPoll ()) { | |
gBS->SignalEvent (Event); | |
} | |
} | |
EFI_STATUS | |
EFIAPI | |
TextInReset ( | |
IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This, | |
IN BOOLEAN ExtendedVerification | |
) | |
{ | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
ReadKeyStroke ( | |
IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This, | |
OUT EFI_INPUT_KEY *Key | |
) | |
{ | |
CHAR8 Char; | |
if (!SerialPortPoll ()) { | |
return EFI_NOT_READY; | |
} | |
SerialPortRead ((UINT8 *)&Char, 1); | |
// | |
// Check for ESC sequence. This code is not technically correct VT100 code. | |
// An illegal ESC sequence represents an ESC and the characters that follow. | |
// This code will eat one or two chars after an escape. This is done to | |
// prevent some complex FIFOing of the data. It is good enough to get | |
// the arrow and delete keys working | |
// | |
Key->UnicodeChar = 0; | |
Key->ScanCode = SCAN_NULL; | |
if (Char == 0x1b) { | |
SerialPortRead ((UINT8 *)&Char, 1); | |
if (Char == '[') { | |
SerialPortRead ((UINT8 *)&Char, 1); | |
switch (Char) { | |
case 'A': | |
Key->ScanCode = SCAN_UP; | |
break; | |
case 'B': | |
Key->ScanCode = SCAN_DOWN; | |
break; | |
case 'C': | |
Key->ScanCode = SCAN_RIGHT; | |
break; | |
case 'D': | |
Key->ScanCode = SCAN_LEFT; | |
break; | |
case 'H': | |
Key->ScanCode = SCAN_HOME; | |
break; | |
case 'K': | |
case 'F': // PC ANSI | |
Key->ScanCode = SCAN_END; | |
break; | |
case '@': | |
case 'L': | |
Key->ScanCode = SCAN_INSERT; | |
break; | |
case 'P': | |
case 'X': // PC ANSI | |
Key->ScanCode = SCAN_DELETE; | |
break; | |
case 'U': | |
case '/': | |
case 'G': // PC ANSI | |
Key->ScanCode = SCAN_PAGE_DOWN; | |
break; | |
case 'V': | |
case '?': | |
case 'I': // PC ANSI | |
Key->ScanCode = SCAN_PAGE_UP; | |
break; | |
// PCANSI that does not conflict with VT100 | |
case 'M': | |
Key->ScanCode = SCAN_F1; | |
break; | |
case 'N': | |
Key->ScanCode = SCAN_F2; | |
break; | |
case 'O': | |
Key->ScanCode = SCAN_F3; | |
break; | |
case 'Q': | |
Key->ScanCode = SCAN_F5; | |
break; | |
case 'R': | |
Key->ScanCode = SCAN_F6; | |
break; | |
case 'S': | |
Key->ScanCode = SCAN_F7; | |
break; | |
case 'T': | |
Key->ScanCode = SCAN_F8; | |
break; | |
default: | |
Key->UnicodeChar = Char; | |
break; | |
} | |
} else if (Char == '0') { | |
SerialPortRead ((UINT8 *)&Char, 1); | |
switch (Char) { | |
case 'P': | |
Key->ScanCode = SCAN_F1; | |
break; | |
case 'Q': | |
Key->ScanCode = SCAN_F2; | |
break; | |
case 'w': | |
Key->ScanCode = SCAN_F3; | |
break; | |
case 'x': | |
Key->ScanCode = SCAN_F4; | |
break; | |
case 't': | |
Key->ScanCode = SCAN_F5; | |
break; | |
case 'u': | |
Key->ScanCode = SCAN_F6; | |
break; | |
case 'q': | |
Key->ScanCode = SCAN_F7; | |
break; | |
case 'r': | |
Key->ScanCode = SCAN_F8; | |
break; | |
case 'p': | |
Key->ScanCode = SCAN_F9; | |
break; | |
case 'm': | |
Key->ScanCode = SCAN_F10; | |
break; | |
default: | |
break; | |
} | |
} | |
} else if (Char < ' ') { | |
if ((Char == CHAR_BACKSPACE) || | |
(Char == CHAR_TAB) || | |
(Char == CHAR_LINEFEED) || | |
(Char == CHAR_CARRIAGE_RETURN)) | |
{ | |
// Only let through EFI required control characters | |
Key->UnicodeChar = (CHAR16)Char; | |
} | |
} else if (Char == 0x7f) { | |
Key->ScanCode = SCAN_DELETE; | |
} else { | |
Key->UnicodeChar = (CHAR16)Char; | |
} | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
TextOutReset ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN BOOLEAN ExtendedVerification | |
) | |
{ | |
EFI_STATUS Status; | |
This->SetAttribute ( | |
This, | |
EFI_TEXT_ATTR (This->Mode->Attribute & 0x0F, EFI_BACKGROUND_BLACK) | |
); | |
Status = This->SetMode (This, 0); | |
return Status; | |
} | |
CHAR8 * | |
EFIAPI | |
SafeUnicodeStrToAsciiStr ( | |
IN CONST CHAR16 *Source, | |
OUT CHAR8 *Destination | |
) | |
{ | |
CHAR8 *ReturnValue; | |
ASSERT (Destination != NULL); | |
// | |
// ASSERT if Source is long than PcdMaximumUnicodeStringLength. | |
// Length tests are performed inside StrLen(). | |
// | |
ASSERT (StrSize (Source) != 0); | |
// | |
// Source and Destination should not overlap | |
// | |
ASSERT ((UINTN)((CHAR16 *)Destination - Source) > StrLen (Source)); | |
ASSERT ((UINTN)((CHAR8 *)Source - Destination) > StrLen (Source)); | |
ReturnValue = Destination; | |
while (*Source != '\0') { | |
// | |
// If any non-ascii characters in Source then replace it with '?'. | |
// | |
if (*Source < 0x80) { | |
*Destination = (CHAR8)*Source; | |
} else { | |
*Destination = '?'; | |
// Surrogate pair check. | |
if ((*Source >= 0xD800) && (*Source <= 0xDFFF)) { | |
Source++; | |
} | |
} | |
Destination++; | |
Source++; | |
} | |
*Destination = '\0'; | |
// | |
// ASSERT Original Destination is less long than PcdMaximumAsciiStringLength. | |
// Length tests are performed inside AsciiStrLen(). | |
// | |
ASSERT (AsciiStrSize (ReturnValue) != 0); | |
return ReturnValue; | |
} | |
EFI_STATUS | |
EFIAPI | |
OutputString ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN CHAR16 *String | |
) | |
{ | |
UINTN Size; | |
CHAR8 *OutputString; | |
EFI_STATUS Status; | |
EFI_SIMPLE_TEXT_OUTPUT_MODE *Mode; | |
UINTN MaxColumn; | |
UINTN MaxRow; | |
Size = StrLen (String) + 1; | |
OutputString = AllocatePool (Size); | |
// If there is any non-ascii characters in String buffer then replace it with '?' | |
// Eventually, UnicodeStrToAsciiStr API should be fixed. | |
SafeUnicodeStrToAsciiStr (String, OutputString); | |
SerialPortWrite ((UINT8 *)OutputString, Size - 1); | |
// | |
// Parse each character of the string to output | |
// to update the cursor position information | |
// | |
Mode = This->Mode; | |
Status = This->QueryMode ( | |
This, | |
Mode->Mode, | |
&MaxColumn, | |
&MaxRow | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
for ( ; *String != CHAR_NULL; String++) { | |
switch (*String) { | |
case CHAR_BACKSPACE: | |
if (Mode->CursorColumn > 0) { | |
Mode->CursorColumn--; | |
} | |
break; | |
case CHAR_LINEFEED: | |
if (Mode->CursorRow < (INT32)(MaxRow - 1)) { | |
Mode->CursorRow++; | |
} | |
break; | |
case CHAR_CARRIAGE_RETURN: | |
Mode->CursorColumn = 0; | |
break; | |
default: | |
if (Mode->CursorColumn >= (INT32)(MaxColumn - 1)) { | |
// Move the cursor as if we print CHAR_CARRIAGE_RETURN & CHAR_LINE_FEED | |
// CHAR_LINEFEED | |
if (Mode->CursorRow < (INT32)(MaxRow - 1)) { | |
Mode->CursorRow++; | |
} | |
// CHAR_CARIAGE_RETURN | |
Mode->CursorColumn = 0; | |
} else { | |
Mode->CursorColumn++; | |
} | |
break; | |
} | |
} | |
FreePool (OutputString); | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
TestString ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN CHAR16 *String | |
) | |
{ | |
CHAR8 Character; | |
for ( ; *String != CHAR_NULL; String++) { | |
Character = (CHAR8)*String; | |
if (!(TextOutIsValidAscii (Character) || TextOutIsValidEfiCntlChar (Character))) { | |
return EFI_UNSUPPORTED; | |
} | |
} | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
QueryMode ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN UINTN ModeNumber, | |
OUT UINTN *Columns, | |
OUT UINTN *Rows | |
) | |
{ | |
if (This->Mode->MaxMode > 1) { | |
return EFI_DEVICE_ERROR; | |
} | |
if (ModeNumber == 0) { | |
*Columns = MODE0_COLUMN_COUNT; | |
*Rows = MODE0_ROW_COUNT; | |
return EFI_SUCCESS; | |
} | |
return EFI_UNSUPPORTED; | |
} | |
EFI_STATUS | |
EFIAPI | |
SetMode ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN UINTN ModeNumber | |
) | |
{ | |
if (ModeNumber != 0) { | |
return EFI_UNSUPPORTED; | |
} | |
This->Mode->Mode = 0; | |
This->ClearScreen (This); | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
SetAttribute ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN UINTN Attribute | |
) | |
{ | |
This->Mode->Attribute = (INT32)Attribute; | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
ClearScreen ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This | |
) | |
{ | |
EFI_STATUS Status; | |
Status = This->SetCursorPosition (This, 0, 0); | |
return Status; | |
} | |
EFI_STATUS | |
EFIAPI | |
SetCursorPosition ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN UINTN Column, | |
IN UINTN Row | |
) | |
{ | |
EFI_SIMPLE_TEXT_OUTPUT_MODE *Mode; | |
EFI_STATUS Status; | |
UINTN MaxColumn; | |
UINTN MaxRow; | |
Mode = This->Mode; | |
Status = This->QueryMode ( | |
This, | |
Mode->Mode, | |
&MaxColumn, | |
&MaxRow | |
); | |
if (EFI_ERROR (Status)) { | |
return EFI_UNSUPPORTED; | |
} | |
if ((Column >= MaxColumn) || (Row >= MaxRow)) { | |
return EFI_UNSUPPORTED; | |
} | |
Mode->CursorColumn = (INT32)Column; | |
Mode->CursorRow = (INT32)Row; | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
EnableCursor ( | |
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, | |
IN BOOLEAN Enable | |
) | |
{ | |
if (!Enable) { | |
return EFI_UNSUPPORTED; | |
} | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
SimpleTextInOutEntryPoint ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_WAIT, | |
TPL_NOTIFY, | |
WaitForKeyEvent, | |
NULL, | |
&mSimpleTextIn.WaitForKey | |
); | |
ASSERT_EFI_ERROR (Status); | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&mInstallHandle, | |
&gEfiSimpleTextInProtocolGuid, | |
&mSimpleTextIn, | |
&gEfiSimpleTextOutProtocolGuid, | |
&mSimpleTextOut, | |
&gEfiDevicePathProtocolGuid, | |
&mDevicePath, | |
NULL | |
); | |
if (!EFI_ERROR (Status)) { | |
gST->ConOut = &mSimpleTextOut; | |
gST->ConIn = &mSimpleTextIn; | |
} | |
return Status; | |
} |