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