Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "VirtualConsole.h"
28#include <AK/String.h>
29#include <Kernel/Arch/i386/CPU.h>
30#include <Kernel/Devices/KeyboardDevice.h>
31#include <Kernel/Heap/kmalloc.h>
32#include <LibBareMetal/IO.h>
33#include <LibBareMetal/StdLib.h>
34
35namespace Kernel {
36
37static u8* s_vga_buffer;
38static VirtualConsole* s_consoles[6];
39static int s_active_console;
40
41void VirtualConsole::get_vga_cursor(u8& row, u8& column)
42{
43 u16 value;
44 IO::out8(0x3d4, 0x0e);
45 value = IO::in8(0x3d5) << 8;
46 IO::out8(0x3d4, 0x0f);
47 value |= IO::in8(0x3d5);
48 row = value / columns();
49 column = value % columns();
50}
51
52void VirtualConsole::flush_vga_cursor()
53{
54 u16 value = m_current_vga_start_address + (m_cursor_row * columns() + m_cursor_column);
55 IO::out8(0x3d4, 0x0e);
56 IO::out8(0x3d5, MSB(value));
57 IO::out8(0x3d4, 0x0f);
58 IO::out8(0x3d5, LSB(value));
59}
60
61void VirtualConsole::initialize()
62{
63 s_vga_buffer = (u8*)0xc00b8000;
64 memset(s_consoles, 0, sizeof(s_consoles));
65 s_active_console = -1;
66}
67
68void VirtualConsole::set_graphical(bool graphical)
69{
70 if (graphical)
71 set_vga_start_row(0);
72
73 m_graphical = graphical;
74}
75
76VirtualConsole::VirtualConsole(unsigned index, InitialContents initial_contents)
77 : TTY(4, index)
78 , m_index(index)
79{
80 sprintf(m_tty_name, "/dev/tty%u", m_index);
81 set_size(80, 25);
82 m_horizontal_tabs = static_cast<u8*>(kmalloc_eternal(columns()));
83 for (unsigned i = 0; i < columns(); ++i)
84 m_horizontal_tabs[i] = (i % 8) == 0;
85 // Rightmost column is always last tab on line.
86 m_horizontal_tabs[columns() - 1] = 1;
87
88 s_consoles[index] = this;
89 m_buffer = (u8*)kmalloc_eternal(rows() * columns() * 2);
90 if (initial_contents == AdoptCurrentVGABuffer) {
91 memcpy(m_buffer, s_vga_buffer, rows() * columns() * 2);
92 get_vga_cursor(m_cursor_row, m_cursor_column);
93 } else {
94 u16* line_mem = reinterpret_cast<u16*>(m_buffer);
95 for (u16 i = 0; i < rows() * columns(); ++i)
96 line_mem[i] = 0x0720;
97 }
98}
99
100VirtualConsole::~VirtualConsole()
101{
102 ASSERT_NOT_REACHED();
103}
104
105void VirtualConsole::clear()
106{
107 u16* linemem = m_active ? (u16*)s_vga_buffer : (u16*)m_buffer;
108 for (u16 i = 0; i < rows() * columns(); ++i)
109 linemem[i] = 0x0720;
110 if (m_active)
111 set_vga_start_row(0);
112 set_cursor(0, 0);
113}
114
115void VirtualConsole::switch_to(unsigned index)
116{
117 if ((int)index == s_active_console)
118 return;
119 ASSERT(index < 6);
120 ASSERT(s_consoles[index]);
121
122 InterruptDisabler disabler;
123 if (s_active_console != -1) {
124 auto* active_console = s_consoles[s_active_console];
125 // We won't know how to switch away from a graphical console until we
126 // can set the video mode on our own. Just stop anyone from trying for
127 // now.
128 if (active_console->is_graphical())
129 return;
130 active_console->set_active(false);
131 }
132 dbg() << "VC: Switch to " << index << " (" << s_consoles[index] << ")";
133 s_active_console = index;
134 s_consoles[s_active_console]->set_active(true);
135 Console::the().set_implementation(s_consoles[s_active_console]);
136}
137
138void VirtualConsole::set_active(bool b)
139{
140 if (b == m_active)
141 return;
142
143 InterruptDisabler disabler;
144
145 m_active = b;
146 if (!m_active) {
147 memcpy(m_buffer, m_current_vga_window, rows() * columns() * 2);
148 KeyboardDevice::the().set_client(nullptr);
149 return;
150 }
151
152 memcpy(s_vga_buffer, m_buffer, rows() * columns() * 2);
153 set_vga_start_row(0);
154 flush_vga_cursor();
155
156 KeyboardDevice::the().set_client(this);
157}
158
159inline bool is_valid_parameter_character(u8 ch)
160{
161 return ch >= 0x30 && ch <= 0x3f;
162}
163
164inline bool is_valid_intermediate_character(u8 ch)
165{
166 return ch >= 0x20 && ch <= 0x2f;
167}
168
169inline bool is_valid_final_character(u8 ch)
170{
171 return ch >= 0x40 && ch <= 0x7e;
172}
173
174enum class VGAColor : u8 {
175 Black = 0,
176 Blue,
177 Green,
178 Cyan,
179 Red,
180 Magenta,
181 Brown,
182 LightGray,
183 DarkGray,
184 BrightBlue,
185 BrightGreen,
186 BrightCyan,
187 BrightRed,
188 BrightMagenta,
189 Yellow,
190 White,
191};
192
193enum class ANSIColor : u8 {
194 Black = 0,
195 Red,
196 Green,
197 Brown,
198 Blue,
199 Magenta,
200 Cyan,
201 LightGray,
202 DarkGray,
203 BrightRed,
204 BrightGreen,
205 Yellow,
206 BrightBlue,
207 BrightMagenta,
208 BrightCyan,
209 White,
210};
211
212static inline VGAColor ansi_color_to_vga(ANSIColor color)
213{
214 switch (color) {
215 case ANSIColor::Black:
216 return VGAColor::Black;
217 case ANSIColor::Red:
218 return VGAColor::Red;
219 case ANSIColor::Brown:
220 return VGAColor::Brown;
221 case ANSIColor::Blue:
222 return VGAColor::Blue;
223 case ANSIColor::Magenta:
224 return VGAColor::Magenta;
225 case ANSIColor::Green:
226 return VGAColor::Green;
227 case ANSIColor::Cyan:
228 return VGAColor::Cyan;
229 case ANSIColor::LightGray:
230 return VGAColor::LightGray;
231 case ANSIColor::DarkGray:
232 return VGAColor::DarkGray;
233 case ANSIColor::BrightRed:
234 return VGAColor::BrightRed;
235 case ANSIColor::BrightGreen:
236 return VGAColor::BrightGreen;
237 case ANSIColor::Yellow:
238 return VGAColor::Yellow;
239 case ANSIColor::BrightBlue:
240 return VGAColor::BrightBlue;
241 case ANSIColor::BrightMagenta:
242 return VGAColor::BrightMagenta;
243 case ANSIColor::BrightCyan:
244 return VGAColor::BrightCyan;
245 case ANSIColor::White:
246 return VGAColor::White;
247 }
248 ASSERT_NOT_REACHED();
249 return VGAColor::LightGray;
250}
251
252static inline u8 ansi_color_to_vga(u8 color)
253{
254 return (u8)ansi_color_to_vga((ANSIColor)color);
255}
256
257void VirtualConsole::escape$m(const Vector<unsigned>& params)
258{
259 for (auto param : params) {
260 switch (param) {
261 case 0:
262 // Reset
263 m_current_attribute = 0x07;
264 break;
265 case 1:
266 // Bold
267 m_current_attribute |= 8;
268 break;
269 case 30:
270 case 31:
271 case 32:
272 case 33:
273 case 34:
274 case 35:
275 case 36:
276 case 37:
277 // Foreground color
278 m_current_attribute &= ~0x7;
279 m_current_attribute |= ansi_color_to_vga(param - 30);
280 break;
281 case 40:
282 case 41:
283 case 42:
284 case 43:
285 case 44:
286 case 45:
287 case 46:
288 case 47:
289 // Background color
290 m_current_attribute &= ~0x70;
291 m_current_attribute |= ansi_color_to_vga(param - 30) << 8;
292 break;
293 }
294 }
295}
296
297void VirtualConsole::escape$s(const Vector<unsigned>&)
298{
299 m_saved_cursor_row = m_cursor_row;
300 m_saved_cursor_column = m_cursor_column;
301}
302
303void VirtualConsole::escape$u(const Vector<unsigned>&)
304{
305 set_cursor(m_saved_cursor_row, m_saved_cursor_column);
306}
307
308void VirtualConsole::escape$H(const Vector<unsigned>& params)
309{
310 unsigned row = 1;
311 unsigned col = 1;
312 if (params.size() >= 1)
313 row = params[0];
314 if (params.size() >= 2)
315 col = params[1];
316 set_cursor(row - 1, col - 1);
317}
318
319void VirtualConsole::escape$A(const Vector<unsigned>& params)
320{
321 int num = 1;
322 if (params.size() >= 1)
323 num = params[0];
324 int new_row = (int)m_cursor_row - num;
325 if (new_row < 0)
326 new_row = 0;
327 set_cursor(new_row, m_cursor_column);
328}
329
330void VirtualConsole::escape$D(const Vector<unsigned>& params)
331{
332 int num = 1;
333 if (params.size() >= 1)
334 num = params[0];
335 int new_column = (int)m_cursor_column - num;
336 if (new_column < 0)
337 new_column = 0;
338 set_cursor(m_cursor_row, new_column);
339}
340
341void VirtualConsole::escape$J(const Vector<unsigned>& params)
342{
343 int mode = 0;
344 if (params.size() >= 1)
345 mode = params[0];
346 switch (mode) {
347 case 0:
348 // FIXME: Clear from cursor to end of screen.
349 ASSERT_NOT_REACHED();
350 break;
351 case 1:
352 // FIXME: Clear from cursor to beginning of screen.
353 ASSERT_NOT_REACHED();
354 break;
355 case 2:
356 clear();
357 break;
358 case 3:
359 // FIXME: <esc>[3J should also clear the scrollback buffer.
360 clear();
361 break;
362 }
363}
364
365void VirtualConsole::execute_escape_sequence(u8 final)
366{
367 auto paramparts = String::copy(m_parameters).split(';');
368 Vector<unsigned> params;
369 for (auto& parampart : paramparts) {
370 bool ok;
371 unsigned value = parampart.to_uint(ok);
372 if (!ok) {
373 // FIXME: Should we do something else?
374 return;
375 }
376 params.append(value);
377 }
378 switch (final) {
379 case 'A':
380 escape$A(params);
381 break;
382 case 'D':
383 escape$D(params);
384 break;
385 case 'H':
386 escape$H(params);
387 break;
388 case 'J':
389 escape$J(params);
390 break;
391 case 'm':
392 escape$m(params);
393 break;
394 case 's':
395 escape$s(params);
396 break;
397 case 'u':
398 escape$u(params);
399 break;
400 default:
401 break;
402 }
403
404 m_parameters.clear();
405 m_intermediates.clear();
406}
407
408void VirtualConsole::clear_vga_row(u16 row)
409{
410 u16* linemem = (u16*)&m_current_vga_window[row * 160];
411 for (u16 i = 0; i < columns(); ++i)
412 linemem[i] = 0x0720;
413}
414
415void VirtualConsole::scroll_up()
416{
417 if (m_cursor_row == (rows() - 1)) {
418 if (m_active) {
419 if (m_vga_start_row >= 160) {
420 memcpy(s_vga_buffer, m_current_vga_window + 160, (rows() - 1) * columns() * 2);
421 set_vga_start_row(0);
422 clear_vga_row(24);
423 } else {
424 set_vga_start_row(m_vga_start_row + 1);
425 clear_vga_row(24);
426 }
427 } else {
428 memmove(m_buffer, m_buffer + 160, 160 * 24);
429 u16* linemem = (u16*)&m_buffer[24 * 160];
430 for (u16 i = 0; i < columns(); ++i)
431 linemem[i] = 0x0720;
432 }
433 } else {
434 ++m_cursor_row;
435 }
436 m_cursor_column = 0;
437}
438
439void VirtualConsole::set_cursor(unsigned row, unsigned column)
440{
441 ASSERT(row < rows());
442 ASSERT(column < columns());
443 m_cursor_row = row;
444 m_cursor_column = column;
445 if (m_active)
446 flush_vga_cursor();
447}
448
449void VirtualConsole::put_character_at(unsigned row, unsigned column, u8 ch)
450{
451 ASSERT(row < rows());
452 ASSERT(column < columns());
453 u16 cur = (row * 160) + (column * 2);
454 if (m_active) {
455 u16 cur = (row * 160) + (column * 2);
456 m_current_vga_window[cur] = ch;
457 m_current_vga_window[cur + 1] = m_current_attribute;
458 } else {
459 m_buffer[cur] = ch;
460 m_buffer[cur + 1] = m_current_attribute;
461 }
462}
463
464void VirtualConsole::on_char(u8 ch)
465{
466 // ignore writes in graphical mode
467 if (m_graphical)
468 return;
469
470 switch (m_escape_state) {
471 case ExpectBracket:
472 if (ch == '[')
473 m_escape_state = ExpectParameter;
474 else
475 m_escape_state = Normal;
476 return;
477 case ExpectParameter:
478 if (is_valid_parameter_character(ch)) {
479 m_parameters.append(ch);
480 return;
481 }
482 m_escape_state = ExpectIntermediate;
483 [[fallthrough]];
484 case ExpectIntermediate:
485 if (is_valid_intermediate_character(ch)) {
486 m_intermediates.append(ch);
487 return;
488 }
489 m_escape_state = ExpectFinal;
490 [[fallthrough]];
491 case ExpectFinal:
492 if (is_valid_final_character(ch)) {
493 m_escape_state = Normal;
494 execute_escape_sequence(ch);
495 return;
496 }
497 m_escape_state = Normal;
498 return;
499 case Normal:
500 break;
501 }
502
503 switch (ch) {
504 case '\0':
505 return;
506 case '\033':
507 m_escape_state = ExpectBracket;
508 return;
509 case 8: // Backspace
510 if (m_cursor_column) {
511 set_cursor(m_cursor_row, m_cursor_column - 1);
512 put_character_at(m_cursor_row, m_cursor_column, ' ');
513 return;
514 }
515 break;
516 case '\a':
517 // FIXME: Bell!
518 return;
519 case '\t': {
520 for (unsigned i = m_cursor_column; i < columns(); ++i) {
521 if (m_horizontal_tabs[i]) {
522 set_cursor(m_cursor_row, i);
523 return;
524 }
525 }
526 return;
527 }
528 case '\n':
529 scroll_up();
530 set_cursor(m_cursor_row, m_cursor_column);
531 return;
532 }
533
534 put_character_at(m_cursor_row, m_cursor_column, ch);
535
536 ++m_cursor_column;
537 if (m_cursor_column >= columns())
538 scroll_up();
539 set_cursor(m_cursor_row, m_cursor_column);
540}
541
542void VirtualConsole::on_key_pressed(KeyboardDevice::Event key)
543{
544 // ignore keyboard in graphical mode
545 if (m_graphical)
546 return;
547
548 if (!key.is_press())
549 return;
550 if (key.ctrl()) {
551 if (key.character >= 'a' && key.character <= 'z') {
552 emit(key.character - 'a' + 1);
553 return;
554 } else if (key.character == '\\') {
555 emit(0x1c);
556 return;
557 }
558 }
559 emit(key.character);
560}
561
562void VirtualConsole::on_sysconsole_receive(u8 ch)
563{
564 InterruptDisabler disabler;
565 auto old_attribute = m_current_attribute;
566 m_current_attribute = 0x03;
567 on_char(ch);
568 m_current_attribute = old_attribute;
569}
570
571ssize_t VirtualConsole::on_tty_write(const u8* data, ssize_t size)
572{
573 InterruptDisabler disabler;
574 for (ssize_t i = 0; i < size; ++i)
575 on_char(data[i]);
576 return size;
577}
578
579StringView VirtualConsole::tty_name() const
580{
581 return m_tty_name;
582}
583
584void VirtualConsole::echo(u8 ch)
585{
586 if (should_echo_input()) {
587 on_tty_write(&ch, 1);
588 }
589}
590
591void VirtualConsole::set_vga_start_row(u16 row)
592{
593 m_vga_start_row = row;
594 m_current_vga_start_address = row * columns();
595 m_current_vga_window = s_vga_buffer + row * 160;
596 IO::out8(0x3d4, 0x0c);
597 IO::out8(0x3d5, MSB(m_current_vga_start_address));
598 IO::out8(0x3d4, 0x0d);
599 IO::out8(0x3d5, LSB(m_current_vga_start_address));
600}
601
602}