]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/efi/console.c
license: LGPL-2.1+ -> LGPL-2.1-or-later
[thirdparty/systemd.git] / src / boot / efi / console.c
CommitLineData
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
15struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL;
16
17typedef 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
22typedef UINT8 EFI_KEY_TOGGLE_STATE;
23
24typedef struct {
25 UINT32 KeyShiftState;
26 EFI_KEY_TOGGLE_STATE KeyToggleState;
27} EFI_KEY_STATE;
28
29typedef struct {
30 EFI_INPUT_KEY Key;
31 EFI_KEY_STATE KeyState;
32} EFI_KEY_DATA;
33
34typedef 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
39typedef 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
44typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)(
b40c3dfa 45 EFI_KEY_DATA *KeyData
0fa2cac4
KS
46);
47
48typedef 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
55typedef 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
60typedef 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
69EFI_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
127static 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
140static 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 156static 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
214EFI_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}