]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
0fa2cac4 KS |
2 | |
3 | #include <efi.h> | |
4 | #include <efilib.h> | |
5 | ||
0fa2cac4 | 6 | #include "console.h" |
cf0fbc49 | 7 | #include "util.h" |
0fa2cac4 | 8 | |
26e51d87 HDA |
9 | #define SYSTEM_FONT_WIDTH 8 |
10 | #define SYSTEM_FONT_HEIGHT 19 | |
11 | ||
0fa2cac4 KS |
12 | #define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \ |
13 | { 0xdd9e7534, 0x7762, 0x4698, { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } } | |
14 | ||
15 | struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; | |
16 | ||
17 | typedef EFI_STATUS (EFIAPI *EFI_INPUT_RESET_EX)( | |
b40c3dfa KS |
18 | struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, |
19 | BOOLEAN ExtendedVerification | |
0fa2cac4 KS |
20 | ); |
21 | ||
22 | typedef UINT8 EFI_KEY_TOGGLE_STATE; | |
23 | ||
24 | typedef struct { | |
25 | UINT32 KeyShiftState; | |
26 | EFI_KEY_TOGGLE_STATE KeyToggleState; | |
27 | } EFI_KEY_STATE; | |
28 | ||
29 | typedef struct { | |
30 | EFI_INPUT_KEY Key; | |
31 | EFI_KEY_STATE KeyState; | |
32 | } EFI_KEY_DATA; | |
33 | ||
34 | typedef EFI_STATUS (EFIAPI *EFI_INPUT_READ_KEY_EX)( | |
b40c3dfa KS |
35 | struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, |
36 | EFI_KEY_DATA *KeyData | |
0fa2cac4 KS |
37 | ); |
38 | ||
39 | typedef EFI_STATUS (EFIAPI *EFI_SET_STATE)( | |
b40c3dfa KS |
40 | struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, |
41 | EFI_KEY_TOGGLE_STATE *KeyToggleState | |
0fa2cac4 KS |
42 | ); |
43 | ||
44 | typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)( | |
b40c3dfa | 45 | EFI_KEY_DATA *KeyData |
0fa2cac4 KS |
46 | ); |
47 | ||
48 | typedef EFI_STATUS (EFIAPI *EFI_REGISTER_KEYSTROKE_NOTIFY)( | |
b40c3dfa KS |
49 | struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, |
50 | EFI_KEY_DATA KeyData, | |
51 | EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction, | |
52 | VOID **NotifyHandle | |
0fa2cac4 KS |
53 | ); |
54 | ||
55 | typedef EFI_STATUS (EFIAPI *EFI_UNREGISTER_KEYSTROKE_NOTIFY)( | |
b40c3dfa KS |
56 | struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, |
57 | VOID *NotificationHandle | |
0fa2cac4 KS |
58 | ); |
59 | ||
60 | typedef struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL { | |
61 | EFI_INPUT_RESET_EX Reset; | |
62 | EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx; | |
63 | EFI_EVENT WaitForKeyEx; | |
64 | EFI_SET_STATE SetState; | |
65 | EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify; | |
66 | EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify; | |
67 | } EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; | |
68 | ||
69 | EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait) { | |
70 | EFI_GUID EfiSimpleTextInputExProtocolGuid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; | |
71 | static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *TextInputEx; | |
72 | static BOOLEAN checked; | |
73 | UINTN index; | |
74 | EFI_INPUT_KEY k; | |
75 | EFI_STATUS err; | |
76 | ||
77 | if (!checked) { | |
78 | err = LibLocateProtocol(&EfiSimpleTextInputExProtocolGuid, (VOID **)&TextInputEx); | |
79 | if (EFI_ERROR(err)) | |
80 | TextInputEx = NULL; | |
81 | ||
82 | checked = TRUE; | |
83 | } | |
84 | ||
85 | /* wait until key is pressed */ | |
03e749af JJ |
86 | if (wait) |
87 | uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index); | |
0fa2cac4 KS |
88 | |
89 | if (TextInputEx) { | |
90 | EFI_KEY_DATA keydata; | |
91 | UINT64 keypress; | |
92 | ||
93 | err = uefi_call_wrapper(TextInputEx->ReadKeyStrokeEx, 2, TextInputEx, &keydata); | |
94 | if (!EFI_ERROR(err)) { | |
95 | UINT32 shift = 0; | |
96 | ||
97 | /* do not distinguish between left and right keys */ | |
98 | if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) { | |
99 | if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED)) | |
100 | shift |= EFI_CONTROL_PRESSED; | |
101 | if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED)) | |
102 | shift |= EFI_ALT_PRESSED; | |
103 | }; | |
104 | ||
105 | /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */ | |
106 | keypress = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar); | |
107 | if (keypress > 0) { | |
108 | *key = keypress; | |
109 | return 0; | |
110 | } | |
111 | } | |
112 | } | |
113 | ||
114 | /* fallback for firmware which does not support SimpleTextInputExProtocol | |
115 | * | |
116 | * This is also called in case ReadKeyStrokeEx did not return a key, because | |
5238e957 | 117 | * some broken firmwares offer SimpleTextInputExProtocol, but never actually |
0fa2cac4 KS |
118 | * handle any key. */ |
119 | err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k); | |
120 | if (EFI_ERROR(err)) | |
121 | return err; | |
122 | ||
123 | *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar); | |
124 | return 0; | |
125 | } | |
68d4b8ac HDA |
126 | |
127 | static EFI_STATUS change_mode(UINTN mode) { | |
128 | EFI_STATUS err; | |
129 | ||
130 | err = uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, mode); | |
131 | ||
132 | /* Special case mode 1: when using OVMF and qemu, setting it returns error | |
133 | * and breaks console output. */ | |
134 | if (EFI_ERROR(err) && mode == 1) | |
135 | uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, (UINTN)0); | |
136 | ||
137 | return err; | |
138 | } | |
139 | ||
26e51d87 HDA |
140 | static UINT64 text_area_from_font_size(void) { |
141 | EFI_STATUS err; | |
142 | UINT64 text_area; | |
143 | UINTN rows, columns; | |
144 | ||
145 | err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &columns, &rows); | |
146 | if (EFI_ERROR(err)) { | |
147 | columns = 80; | |
148 | rows = 25; | |
149 | } | |
150 | ||
151 | text_area = SYSTEM_FONT_WIDTH * SYSTEM_FONT_HEIGHT * (UINT64)rows * (UINT64)columns; | |
152 | ||
153 | return text_area; | |
154 | } | |
155 | ||
68d4b8ac | 156 | static EFI_STATUS mode_auto(UINTN *mode) { |
26e51d87 HDA |
157 | const UINT32 HORIZONTAL_MAX_OK = 1920; |
158 | const UINT32 VERTICAL_MAX_OK = 1080; | |
159 | const UINT64 VIEWPORT_RATIO = 10; | |
160 | UINT64 screen_area, text_area; | |
161 | EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; | |
162 | EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; | |
163 | EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; | |
164 | EFI_STATUS err; | |
165 | BOOLEAN keep = FALSE; | |
166 | ||
167 | err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput); | |
168 | if (!EFI_ERROR(err) && GraphicsOutput->Mode && GraphicsOutput->Mode->Info) { | |
169 | Info = GraphicsOutput->Mode->Info; | |
170 | ||
171 | /* Start verifying if we are in a resolution larger than Full HD | |
172 | * (1920x1080). If we're not, assume we're in a good mode and do not | |
173 | * try to change it. */ | |
174 | if (Info->HorizontalResolution <= HORIZONTAL_MAX_OK && Info->VerticalResolution <= VERTICAL_MAX_OK) | |
175 | keep = TRUE; | |
176 | /* For larger resolutions, calculate the ratio of the total screen | |
177 | * area to the text viewport area. If it's less than 10 times bigger, | |
178 | * then assume the text is readable and keep the text mode. */ | |
179 | else { | |
180 | screen_area = (UINT64)Info->HorizontalResolution * (UINT64)Info->VerticalResolution; | |
181 | text_area = text_area_from_font_size(); | |
182 | ||
183 | if (text_area != 0 && screen_area/text_area < VIEWPORT_RATIO) | |
184 | keep = TRUE; | |
185 | } | |
186 | } | |
187 | ||
188 | if (keep) { | |
189 | /* Just clear the screen instead of changing the mode and return. */ | |
190 | *mode = ST->ConOut->Mode->Mode; | |
191 | uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); | |
192 | return EFI_SUCCESS; | |
193 | } | |
194 | ||
195 | /* If we reached here, then we have a high resolution screen and the text | |
196 | * viewport is less than 10% the screen area, so the firmware developer | |
197 | * screwed up. Try to switch to a better mode. Mode number 2 is first non | |
198 | * standard mode, which is provided by the device manufacturer, so it should | |
199 | * be a good mode. | |
68d4b8ac HDA |
200 | * Note: MaxMode is the number of modes, not the last mode. */ |
201 | if (ST->ConOut->Mode->MaxMode > 2) | |
202 | *mode = 2; | |
203 | /* Try again with mode different than zero (assume user requests | |
204 | * auto mode due to some problem with mode zero). */ | |
205 | else if (ST->ConOut->Mode->MaxMode == 2) | |
206 | *mode = 1; | |
207 | /* Else force mode change to zero. */ | |
208 | else | |
209 | *mode = 0; | |
210 | ||
211 | return change_mode(*mode); | |
212 | } | |
213 | ||
214 | EFI_STATUS console_set_mode(UINTN *mode, enum console_mode_change_type how) { | |
215 | if (how == CONSOLE_MODE_AUTO) | |
216 | return mode_auto(mode); | |
217 | ||
218 | if (how == CONSOLE_MODE_MAX) { | |
219 | /* Note: MaxMode is the number of modes, not the last mode. */ | |
220 | if (ST->ConOut->Mode->MaxMode > 0) | |
221 | *mode = ST->ConOut->Mode->MaxMode-1; | |
222 | else | |
223 | *mode = 0; | |
224 | } | |
225 | ||
226 | return change_mode(*mode); | |
227 | } |