1/*
2 * Copyright 2014-2016 Haiku, Inc. All rights reserved.
3 * Copyright 2013 Fredrik Holmqvist, fredrik.holmqvist@gmail.com. All rights
4 * reserved.
5 * Distributed under the terms of the MIT License.
6 */
7
8
9#include "console.h"
10
11#include <string.h>
12
13#include <SupportDefs.h>
14
15#include <boot/stage2.h>
16#include <boot/platform.h>
17#include <boot/platform/generic/video.h>
18#include <efi/protocol/console-control.h>
19#include <util/kernel_cpp.h>
20
21#include "efi_platform.h"
22
23
24// This likely won't work without moving things around.
25// Too early (pre-console init)
26//#define TRACE_CONSOLE
27#ifdef TRACE_CONSOLE
28#   define TRACE(x...) dprintf(x)
29#else
30#   define TRACE(x...)
31#endif
32
33
34class EFITextConsole : public ConsoleNode {
35	public:
36		EFITextConsole();
37
38		virtual ssize_t ReadAt(void *cookie, off_t pos, void *buffer,
39			size_t bufferSize);
40		virtual ssize_t WriteAt(void *cookie, off_t pos, const void *buffer,
41			size_t bufferSize);
42
43		virtual void	ClearScreen();
44		virtual int32	Width();
45		virtual int32	Height();
46		virtual void	SetCursor(int32 x, int32 y);
47		virtual void	SetCursorVisible(bool visible);
48		virtual void	SetColors(int32 foreground, int32 background);
49
50	public:
51		uint32 fScreenWidth, fScreenHeight;
52};
53
54
55extern ConsoleNode* gConsoleNode;
56static uint32 sScreenMode;
57static EFITextConsole sConsole;
58FILE *stdin, *stdout, *stderr;
59
60
61//	#pragma mark -
62
63
64EFITextConsole::EFITextConsole()
65	: ConsoleNode()
66{
67}
68
69
70ssize_t
71EFITextConsole::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize)
72{
73	return B_ERROR;
74}
75
76
77ssize_t
78EFITextConsole::WriteAt(void *cookie, off_t /*pos*/, const void *buffer,
79	size_t bufferSize)
80{
81	const char *string = (const char *)buffer;
82	char16_t ucsBuffer[bufferSize + 3];
83	uint32 j = 0;
84
85	for (uint32 i = 0; i < bufferSize; i++) {
86		switch (string[i]) {
87			case '\n': {
88				ucsBuffer[j++] = '\r';
89				ucsBuffer[j++] = '\n';
90			} //fallthrough
91			case 0 : {
92				//Not sure if we should keep going or abort for 0.
93				//Keep going was easy anyway.
94				ucsBuffer[j] = 0;
95				kSystemTable->ConOut->OutputString(kSystemTable->ConOut,
96					ucsBuffer);
97				j = 0;
98				continue;
99			}
100			default:
101				ucsBuffer[j++] = (char16_t)string[i];
102		}
103	}
104
105	if (j > 0) {
106		ucsBuffer[j] = 0;
107		kSystemTable->ConOut->OutputString(kSystemTable->ConOut, ucsBuffer);
108	}
109	return bufferSize;
110}
111
112
113void
114EFITextConsole::ClearScreen()
115{
116	kSystemTable->ConOut->ClearScreen(kSystemTable->ConOut);
117}
118
119
120int32
121EFITextConsole::Width()
122{
123	return fScreenWidth;
124}
125
126
127int32
128EFITextConsole::Height()
129{
130	return fScreenHeight;
131}
132
133
134void
135EFITextConsole::SetCursor(int32 x, int32 y)
136{
137	kSystemTable->ConOut->SetCursorPosition(kSystemTable->ConOut, x, y);
138}
139
140
141void
142EFITextConsole::SetCursorVisible(bool visible)
143{
144	kSystemTable->ConOut->EnableCursor(kSystemTable->ConOut, visible);
145}
146
147
148void
149EFITextConsole::SetColors(int32 foreground, int32 background)
150{
151	kSystemTable->ConOut->SetAttribute(kSystemTable->ConOut,
152		EFI_TEXT_ATTR((foreground & 0xf), (background & 0xf)));
153}
154
155
156int
157console_wait_for_key(void)
158{
159	size_t index;
160	efi_status status;
161	efi_input_key key;
162	efi_event event = kSystemTable->ConIn->WaitForKey;
163
164	do {
165		kBootServices->WaitForEvent(1, &event, &index);
166		status = kSystemTable->ConIn->ReadKeyStroke(kSystemTable->ConIn, &key);
167	} while (status == EFI_NOT_READY);
168
169	if (key.UnicodeChar > 0)
170		return (int) key.UnicodeChar;
171
172	switch (key.ScanCode) {
173		case SCAN_ESC:
174			return TEXT_CONSOLE_KEY_ESCAPE;
175		case SCAN_UP:
176			return TEXT_CONSOLE_KEY_UP;
177		case SCAN_DOWN:
178			return TEXT_CONSOLE_KEY_DOWN;
179		case SCAN_LEFT:
180			return TEXT_CONSOLE_KEY_LEFT;
181		case SCAN_RIGHT:
182			return TEXT_CONSOLE_KEY_RIGHT;
183		case SCAN_PAGE_UP:
184			return TEXT_CONSOLE_KEY_PAGE_UP;
185		case SCAN_PAGE_DOWN:
186			return TEXT_CONSOLE_KEY_PAGE_DOWN;
187		case SCAN_HOME:
188			return TEXT_CONSOLE_KEY_HOME;
189		case SCAN_END:
190			return TEXT_CONSOLE_KEY_END;
191	}
192	return 0;
193}
194
195
196static void update_screen_size(void)
197{
198	size_t width, height;
199	size_t area = 0;
200	efi_simple_text_output_protocol *ConOut = kSystemTable->ConOut;
201
202	for (int mode = 0; mode < ConOut->Mode->MaxMode; ++mode) {
203		if (ConOut->QueryMode(ConOut, mode, &width, &height) == EFI_SUCCESS) {
204			if (width * height > area) {
205				sConsole.fScreenWidth = width;
206				sConsole.fScreenHeight = height;
207				sScreenMode = mode;
208			}
209		}
210	}
211
212	ConOut->SetMode(ConOut, sScreenMode);
213}
214
215
216status_t
217console_init(void)
218{
219#if 1
220	gConsoleNode = &sConsole;
221
222	update_screen_size();
223	console_hide_cursor();
224	console_clear_screen();
225#else
226	// FIXME: This does not work because we cannot initialize video before VFS, as it
227	// needs to read the driver settings before setting a mode; and also because the
228	// heap does not yet exist.
229	platform_init_video();
230	platform_switch_to_logo();
231	gConsoleNode = video_text_console_init(gKernelArgs.frame_buffer.physical_buffer.start);
232#endif
233
234	// enable stdio functionality
235	stdin = (FILE *)gConsoleNode;
236	stdout = stderr = (FILE *)gConsoleNode;
237
238	return B_OK;
239}
240
241
242uint32
243console_check_boot_keys(void)
244{
245	efi_input_key key;
246
247	for (int i = 0; i < 3; i++) {
248		// give the user a chance to press a key
249		kBootServices->Stall(100000);
250
251		efi_status status = kSystemTable->ConIn->ReadKeyStroke(
252			kSystemTable->ConIn, &key);
253
254		if (status != EFI_SUCCESS)
255			continue;
256
257		if (key.UnicodeChar == 0 && key.ScanCode == SCAN_ESC)
258			return BOOT_OPTION_DEBUG_OUTPUT;
259		if (key.UnicodeChar == ' ')
260			return BOOT_OPTION_MENU;
261	}
262	return 0;
263}
264
265
266extern "C" void
267platform_switch_to_text_mode(void)
268{
269	kSystemTable->ConOut->Reset(kSystemTable->ConOut, false);
270	kSystemTable->ConOut->SetMode(kSystemTable->ConOut, sScreenMode);
271}
272