Two ANSI bugs, both found by running fastfetch rather than by reading the parser, and both invisible to anything that spells its escapes out in full. ESC[m with no parameters means ESC[0m. ECMA-48 is explicit that an omitted parameter takes its default and SGR's default is 0, and sgr() iterated its parameter list -- which is empty in exactly that case, so the loop body never ran and the reset was silently dropped. Programs emit the bare form constantly. fastfetch writes "\x1b[32m1%\x1b[m", so everything after the first coloured character stayed green forever. With a background colour it is much worse: its palette swatch sets a white background and resets, the reset did nothing, and the console went white-on-white and stayed there -- filling the screen with white rectangles as it scrolled, which looks like a corrupted framebuffer rather than one missing branch. OSC sequences were not a state at all, so ESC ] fell through and the payload printed as text: a hyperlinked path arrived as "Disk (]8;;file:///\]8;;\)". Nothing here acts on an operating-system command -- there is no window title and no hyperlink to follow -- but *discarding* one is not optional, because the alternative is printing somebody's URL into the middle of their output. ESC \ ends it, and an ESC followed by anything else is handed back to the escape state rather than eaten, so a malformed OSC does not swallow the sequence after it. Verified by screendumping the framebuffer after two consecutive fastfetch runs: identical output both times, near-white coverage down from most of the screen to 2% (the palette swatch's own white cell), and the disk line reading "Disk (/)". Also installs docs/branding/Ascii/ascii_50.txt as /usr/share/schos/logo.txt -- Shift as characters, at the width that leaves room for an info block on an 80-column console. Installed from the branding directory rather than copied into sysroot by hand, so there is one source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
816 lines
25 KiB
C
816 lines
25 KiB
C
#include <stdarg.h>
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "gfx.h"
|
|
#include "theme.h"
|
|
#include "irq.h"
|
|
#include "param.h"
|
|
#include "traps.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "file.h"
|
|
#include "memlayout.h"
|
|
#include "mmu.h"
|
|
#include "proc.h"
|
|
#include "posix.h"
|
|
#include "x86.h"
|
|
|
|
void consputc(int);
|
|
// Set by panic() once the crash screen has been painted: after that, consputc
|
|
// spins instead of writing, so nothing else can scribble over the report.
|
|
int panicked = 0;
|
|
|
|
// When set, console output goes to the serial line only and the framebuffer
|
|
// is left alone.
|
|
//
|
|
// **Starts set, on purpose.** The display is the product; the serial line is
|
|
// the diagnostics. A machine that boots by printing "pci: 6 devices" and
|
|
// "vga: display controller 1234:1111, aperture fd000000" to the screen the
|
|
// user is looking at is showing them a developer's log, and that single fact
|
|
// is most of what makes an OS look like a hobby project.
|
|
//
|
|
// Nothing is lost: every one of those lines still goes out the UART, which is
|
|
// where tools/bootcheck.ps1 reads them from and where you want them when
|
|
// something goes wrong. console_takeover() clears this once userspace is up
|
|
// and the display genuinely belongs to the console. The F8 verbose boot never
|
|
// sets it, so "show me everything" still works.
|
|
int cons_serial_only = 1;
|
|
extern void gfx_screen_reset(void);
|
|
|
|
// Text colour used when drawing to the framebuffer. Changed by SGR escapes
|
|
// (see sgr() below); reset to the default by ESC[0m and ESC c.
|
|
//
|
|
// This was initialised to 7, a leftover from when it was a VGA text-mode
|
|
// attribute byte. gfx_putc takes a 32-bit RGB value, so 7 meant "very dark
|
|
// blue" -- it looked black against the cream background purely by accident.
|
|
static uint32 cons_fg = SCH_FG;
|
|
|
|
static struct {
|
|
struct spinlock lock;
|
|
int locking;
|
|
} cons;
|
|
|
|
static char digits[] = "0123456789abcdef";
|
|
|
|
static void
|
|
print_int(int64 xx, int base, int sign)
|
|
{
|
|
char buf[64];
|
|
int i;
|
|
uint64 x;
|
|
|
|
if(sign && (sign = (xx < 0)))
|
|
x = -xx;
|
|
else
|
|
x = xx;
|
|
|
|
i = 0;
|
|
do {
|
|
buf[i++] = digits[x % base];
|
|
} while((x /= base) != 0);
|
|
|
|
if(sign)
|
|
buf[i++] = '-';
|
|
|
|
while(--i >= 0)
|
|
consputc(buf[i]);
|
|
}
|
|
|
|
void
|
|
cprintf(char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int i, c, locking;
|
|
char *s;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
locking = cons.locking;
|
|
if (locking)
|
|
acquire(&cons.lock);
|
|
|
|
if (fmt == 0)
|
|
panic("null fmt");
|
|
|
|
for (i = 0; (c = fmt[i] & 0xff) != 0; i++) {
|
|
if (c != '%') {
|
|
consputc(c);
|
|
continue;
|
|
}
|
|
c = fmt[++i] & 0xff;
|
|
if (c == 0)
|
|
break;
|
|
switch(c) {
|
|
case 'd':
|
|
print_int(va_arg(ap, int), 10, 1);
|
|
break;
|
|
case 'u':
|
|
print_int(va_arg(ap, uint32), 10, 0);
|
|
break;
|
|
case 'l':
|
|
print_int(va_arg(ap, int64), 10, 1);
|
|
break;
|
|
case 'x':
|
|
print_int(va_arg(ap, uint32), 16, 0);
|
|
break;
|
|
case 'p':
|
|
print_int(va_arg(ap, uint64), 16, 0);
|
|
break;
|
|
case 'b':
|
|
print_int(va_arg(ap, uint32), 2, 0);
|
|
break;
|
|
case 's':
|
|
if ((s = va_arg(ap, char*)) == 0)
|
|
s = "(null)";
|
|
while (*s)
|
|
consputc(*(s++));
|
|
break;
|
|
case '%':
|
|
consputc('%');
|
|
break;
|
|
default:
|
|
consputc('%');
|
|
consputc(c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (locking)
|
|
release(&cons.lock);
|
|
}
|
|
|
|
// Hand the console over to panic().
|
|
//
|
|
// Two things change. The lock stops being taken, so a panic can report itself
|
|
// from anywhere — including from inside a routine that already holds it. And
|
|
// output stops going to the framebuffer, because from here on the framebuffer
|
|
// belongs to the crash screen: without this the report would scroll straight
|
|
// over the panel that is trying to display it.
|
|
void
|
|
cons_panic_mode(void)
|
|
{
|
|
cons.locking = 0;
|
|
cons_serial_only = 1;
|
|
}
|
|
|
|
// Hand the display over to the text console.
|
|
//
|
|
// Called once, when userspace is far enough along that the screen should stop
|
|
// being a boot splash and start being a terminal. Until this runs the kernel
|
|
// has been talking to the serial line only (see cons_serial_only), so this is
|
|
// the moment the framebuffer changes owner -- which is why it clears first
|
|
// rather than scrolling the shell prompt in underneath the splash.
|
|
void
|
|
console_takeover(void)
|
|
{
|
|
if (!cons_serial_only)
|
|
return; // verbose boot: the console never left
|
|
cons_serial_only = 0;
|
|
gfx_screen_reset();
|
|
}
|
|
|
|
// Foreground process group of the console, i.e. the controlling terminal.
|
|
// tcgetpgrp()/tcsetpgrp() read and write this through TIOCGPGRP/TIOCSPGRP in
|
|
// sys_fs.c. Initialised to 1 -- init's process group -- so the first shell,
|
|
// which inherits that group, already sees itself as the foreground job and
|
|
// does not immediately stop itself with SIGTTIN.
|
|
int tty_fg_pgrp = 1;
|
|
|
|
#define BACKSPACE 0x100
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Output: an ANSI escape parser, and UTF-8
|
|
//
|
|
// The console used to print every byte it was given, which meant every escape
|
|
// sequence any program emitted appeared on screen as literal text -- `clear`
|
|
// drew "^[[2J^[[H" and left the screen exactly as it found it. Colour, cursor
|
|
// addressing, and anything full-screen were all equally impossible.
|
|
//
|
|
// The serial line does NOT go through any of this. On the other end of it is
|
|
// a real terminal emulator that already understands ANSI far better than this
|
|
// ever will, so escapes are passed through to it verbatim and interpreted
|
|
// only for the framebuffer. Two consumers, two right answers.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// The sixteen ANSI colours as RGB, for a linear framebuffer.
|
|
//
|
|
// consoleinit() used to program these into the VGA DAC with set_vga_palette(),
|
|
// which does nothing at all in a 32bpp linear mode -- there is no palette to
|
|
// index. The colours had to live somewhere real, and this is it.
|
|
//
|
|
// That DAC code is now gone rather than merely unused, along with the blink
|
|
// disable next to it: both were writes to VGA hardware registers made from the
|
|
// console, which is neither a driver nor allowed to know it was running on a
|
|
// VGA. Nothing observable changed, because nothing observable depended on it.
|
|
static const uint32 ansi_rgb[16] = {
|
|
0x000000, 0xAA0000, 0x00AA00, 0xAA5500, // black red green yellow(brown)
|
|
0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, // blue magenta cyan white
|
|
0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, // bright versions
|
|
0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF,
|
|
};
|
|
|
|
#define CONS_DEFAULT_FG SCH_FG // see include/theme.h
|
|
#define CONS_DEFAULT_BG SCH_BG
|
|
|
|
static uint32 cons_bg = CONS_DEFAULT_BG;
|
|
static int cons_bold = 0;
|
|
|
|
// Escape parser state.
|
|
// E_OSC and E_OSC_ESC exist for hyperlinks. A terminal that does not follow
|
|
// them must still *consume* them, or the URL is printed as text -- which is
|
|
// what "Disk (]8;;file:///\]8;;\): 4.6 MiB" was: fastfetch wrapping a path in
|
|
// OSC 8, and this console spelling it out.
|
|
enum { E_NORMAL, E_ESC, E_CSI, E_OSC, E_OSC_ESC };
|
|
static int esc_state = E_NORMAL;
|
|
static int esc_parm[8];
|
|
static int esc_nparm;
|
|
static int esc_private; // saw '?' after CSI
|
|
static int saved_col, saved_row;
|
|
|
|
// UTF-8 decoder state. A codepoint arrives as up to four bytes and has to be
|
|
// reassembled before it can be turned into a glyph.
|
|
static uint32 u8_cp;
|
|
static int u8_need;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Unicode -> glyph
|
|
//
|
|
// The font's upper half is CP437, not Latin-1, so a codepoint cannot be used
|
|
// as an index. It looks like it can -- font.h's own comments say so, labelling
|
|
// each glyph with the Unicode character of the same number -- but the bitmaps
|
|
// disagree: index 0xE9 draws a capital theta, and rendering U+00E9 there turns
|
|
// "cafe" into "caf" followed by a theta. Every accented character came out as
|
|
// an unrelated symbol, consistently enough to look almost deliberate.
|
|
//
|
|
// So: a table. Entry i covers codepoint 0x80 + i, and 0 means "this font has
|
|
// no glyph for that". The C1 range (0x80-0x9F) has no printable characters at
|
|
// all, hence the leading run of zeros.
|
|
// ---------------------------------------------------------------------------
|
|
static const uchar latin1_to_cp437[128] = {
|
|
/* 0x80-0x9F: C1 controls, nothing to draw */
|
|
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
|
/* 0xA0 */ 0x20, 0xAD, 0x9B, 0x9C, 0, 0x9D, 0, 0x15,
|
|
/* 0xA8 */ 0, 0, 0xA6, 0xAE, 0xAA, 0x2D, 0, 0,
|
|
/* 0xB0 */ 0xF8, 0xF1, 0xFD, 0, 0, 0xE6, 0x14, 0xFA,
|
|
/* 0xB8 */ 0, 0, 0xA7, 0xAF, 0xAC, 0xAB, 0, 0xA8,
|
|
/* 0xC0 */ 0, 0, 0, 0, 0x8E, 0x8F, 0x92, 0x80,
|
|
/* 0xC8 */ 0, 0x90, 0, 0, 0, 0, 0, 0,
|
|
/* 0xD0 */ 0, 0xA5, 0, 0, 0, 0, 0x99, 0,
|
|
/* 0xD8 */ 0, 0, 0, 0, 0x9A, 0, 0, 0xE1,
|
|
/* 0xE0 */ 0x85, 0xA0, 0x83, 0, 0x84, 0x86, 0x91, 0x87,
|
|
/* 0xE8 */ 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B,
|
|
/* 0xF0 */ 0, 0xA4, 0x95, 0xA2, 0x93, 0, 0x94, 0xF6,
|
|
/* 0xF8 */ 0, 0x97, 0xA3, 0x96, 0x81, 0, 0, 0x98,
|
|
};
|
|
|
|
// The handful of characters above U+00FF that CP437 does have. Box drawing
|
|
// and block elements matter because they are what every text-mode program
|
|
// draws frames and progress bars out of.
|
|
struct cp_pair { uint32 cp; uchar glyph; };
|
|
static const struct cp_pair cp437_extra[] = {
|
|
{ 0x2022, 0x07 }, /* bullet */
|
|
{ 0x2500, 0xC4 }, { 0x2502, 0xB3 }, /* light horizontal/vertical */
|
|
{ 0x250C, 0xDA }, { 0x2510, 0xBF },
|
|
{ 0x2514, 0xC0 }, { 0x2518, 0xD9 },
|
|
{ 0x251C, 0xC3 }, { 0x2524, 0xB4 },
|
|
{ 0x252C, 0xC2 }, { 0x2534, 0xC1 }, { 0x253C, 0xC5 },
|
|
{ 0x2550, 0xCD }, { 0x2551, 0xBA }, /* double */
|
|
{ 0x2554, 0xC9 }, { 0x2557, 0xBB },
|
|
{ 0x255A, 0xC8 }, { 0x255D, 0xBC },
|
|
{ 0x2580, 0xDF }, { 0x2584, 0xDC }, { 0x2588, 0xDB },
|
|
{ 0x258C, 0xDD }, { 0x2590, 0xDE },
|
|
{ 0x2591, 0xB0 }, { 0x2592, 0xB1 }, { 0x2593, 0xB2 },
|
|
{ 0x25A0, 0xFE }, /* black square */
|
|
{ 0x221A, 0xFB }, { 0x221E, 0xEC }, /* root, infinity */
|
|
{ 0x2260, 0, }, { 0x2264, 0xF3 }, { 0x2265, 0xF2 },
|
|
{ 0x2248, 0xF7 }, { 0x2261, 0xF0 },
|
|
};
|
|
|
|
// Returns the glyph to draw for a codepoint, or 0 if there is none.
|
|
static int
|
|
glyph_for(uint32 cp)
|
|
{
|
|
if (cp < 0x80)
|
|
return (int)cp; // ASCII is CP437's lower half
|
|
if (cp < 0x100)
|
|
return latin1_to_cp437[cp - 0x80];
|
|
|
|
for (unsigned i = 0; i < NELEM(cp437_extra); i++)
|
|
if (cp437_extra[i].cp == cp)
|
|
return cp437_extra[i].glyph;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
sgr(void)
|
|
{
|
|
// "ESC[m" with no parameters means ESC[0m -- reset -- and ECMA-48 says so for
|
|
// every CSI: an omitted parameter takes its default, and SGR's default is 0.
|
|
//
|
|
// This loop used to start at a count of zero and therefore do nothing, so a
|
|
// bare reset was silently dropped. Programs emit it constantly: fastfetch
|
|
// writes "\x1b[32m1%\x1b[m", and with the reset ignored everything after the
|
|
// first coloured character stayed green forever. Worse with background
|
|
// colours -- its palette swatch sets a white background and resets, so the
|
|
// whole console went white-on-white and stayed there, which looks like a
|
|
// corrupted framebuffer rather than a missing branch.
|
|
int nparm = esc_nparm ? esc_nparm : 1; // esc_parm[0] is already 0
|
|
|
|
for (int i = 0; i < nparm; i++) {
|
|
int p = esc_parm[i];
|
|
|
|
if (p == 0) {
|
|
cons_fg = CONS_DEFAULT_FG;
|
|
cons_bg = CONS_DEFAULT_BG;
|
|
cons_bold = 0;
|
|
} else if (p == 1) {
|
|
cons_bold = 1;
|
|
// Re-apply brightness to a colour already chosen.
|
|
for (int j = 0; j < 8; j++)
|
|
if (cons_fg == ansi_rgb[j]) { cons_fg = ansi_rgb[j + 8]; break; }
|
|
} else if (p == 22) {
|
|
cons_bold = 0;
|
|
} else if (p >= 30 && p <= 37) {
|
|
cons_fg = ansi_rgb[(p - 30) + (cons_bold ? 8 : 0)];
|
|
} else if (p == 39) {
|
|
cons_fg = CONS_DEFAULT_FG;
|
|
} else if (p >= 40 && p <= 47) {
|
|
cons_bg = ansi_rgb[p - 40];
|
|
} else if (p == 49) {
|
|
cons_bg = CONS_DEFAULT_BG;
|
|
} else if (p >= 90 && p <= 97) {
|
|
cons_fg = ansi_rgb[(p - 90) + 8];
|
|
} else if (p >= 100 && p <= 107) {
|
|
cons_bg = ansi_rgb[(p - 100) + 8];
|
|
}
|
|
}
|
|
gfx_set_bg(cons_bg);
|
|
}
|
|
|
|
// One final byte of a CSI sequence, with esc_parm[] already filled in.
|
|
static void
|
|
csi_dispatch(int final)
|
|
{
|
|
int col, row;
|
|
int n = esc_nparm > 0 ? esc_parm[0] : 0;
|
|
int n1 = n ? n : 1; // most sequences default to 1
|
|
|
|
gfx_cursor_get(&col, &row);
|
|
|
|
switch (final) {
|
|
case 'A': gfx_cursor_set(col, row - n1); break;
|
|
case 'B': gfx_cursor_set(col, row + n1); break;
|
|
case 'C': gfx_cursor_set(col + n1, row); break;
|
|
case 'D': gfx_cursor_set(col - n1, row); break;
|
|
case 'E': gfx_cursor_set(0, row + n1); break;
|
|
case 'F': gfx_cursor_set(0, row - n1); break;
|
|
case 'G': gfx_cursor_set(n1 - 1, row); break;
|
|
case 'd': gfx_cursor_set(col, n1 - 1); break;
|
|
|
|
case 'H': case 'f': {
|
|
int r = esc_nparm > 0 && esc_parm[0] ? esc_parm[0] : 1;
|
|
int c = esc_nparm > 1 && esc_parm[1] ? esc_parm[1] : 1;
|
|
gfx_cursor_set(c - 1, r - 1); // ANSI counts from 1
|
|
break;
|
|
}
|
|
|
|
case 'J': gfx_erase_display(n); break;
|
|
case 'K': gfx_erase_line(n); break;
|
|
|
|
case 'm': sgr(); break;
|
|
|
|
case 's': saved_col = col; saved_row = row; break;
|
|
case 'u': gfx_cursor_set(saved_col, saved_row); break;
|
|
|
|
// Cursor visibility (DECTCEM) and the other private modes. There is no
|
|
// hardware cursor being drawn, so there is nothing to show or hide --
|
|
// swallowing them silently is the correct behaviour, and far better than
|
|
// printing "?25l" into the middle of someone's output.
|
|
case 'h': case 'l': break;
|
|
|
|
default: break; // unknown: drop it, do not print it
|
|
}
|
|
}
|
|
|
|
// Feed one output byte to the framebuffer side. Returns nothing; the serial
|
|
// side has already had the raw byte.
|
|
static void
|
|
gfx_feed(int c)
|
|
{
|
|
switch (esc_state) {
|
|
case E_ESC:
|
|
if (c == '[') {
|
|
esc_state = E_CSI;
|
|
esc_nparm = 0;
|
|
esc_private = 0;
|
|
for (int i = 0; i < 8; i++) esc_parm[i] = 0;
|
|
return;
|
|
}
|
|
if (c == ']') { // OSC: swallow it, see below
|
|
esc_state = E_OSC;
|
|
return;
|
|
}
|
|
esc_state = E_NORMAL;
|
|
if (c == 'c') { // RIS -- full reset
|
|
cons_fg = CONS_DEFAULT_FG;
|
|
cons_bg = CONS_DEFAULT_BG;
|
|
gfx_screen_reset();
|
|
}
|
|
return; // other two-byte escapes: ignored
|
|
|
|
// An operating-system command runs until BEL or until ST (ESC \). Nothing
|
|
// here acts on one -- there is no window title to set and no hyperlink to
|
|
// follow -- but discarding the payload is not optional: the alternative is
|
|
// printing somebody's URL into the middle of their output.
|
|
case E_OSC:
|
|
if (c == '\a') { esc_state = E_NORMAL; return; }
|
|
if (c == '\x1b') { esc_state = E_OSC_ESC; return; }
|
|
return;
|
|
|
|
case E_OSC_ESC:
|
|
// ESC \ ends it; ESC anything-else means the OSC was malformed and a new
|
|
// escape has started, so hand the byte to the ESC state rather than eating
|
|
// it. Dropping it would lose the sequence that follows.
|
|
if (c == '\\') { esc_state = E_NORMAL; return; }
|
|
esc_state = E_ESC;
|
|
gfx_feed(c);
|
|
return;
|
|
|
|
case E_CSI:
|
|
if (c == '?') { esc_private = 1; return; }
|
|
if (c >= '0' && c <= '9') {
|
|
if (esc_nparm == 0) esc_nparm = 1;
|
|
if (esc_nparm <= 8)
|
|
esc_parm[esc_nparm - 1] = esc_parm[esc_nparm - 1] * 10 + (c - '0');
|
|
return;
|
|
}
|
|
if (c == ';') {
|
|
if (esc_nparm < 8) esc_nparm++;
|
|
return;
|
|
}
|
|
esc_state = E_NORMAL;
|
|
if (!esc_private)
|
|
csi_dispatch(c);
|
|
return;
|
|
}
|
|
|
|
// E_NORMAL
|
|
if (c == '\x1b') { esc_state = E_ESC; return; }
|
|
|
|
// UTF-8 continuation. A codepoint the font cannot draw becomes '?' -- the
|
|
// one thing it must not become is a *different* character, which is exactly
|
|
// what indexing the font by codepoint did.
|
|
if (u8_need > 0) {
|
|
if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence
|
|
u8_need = 0;
|
|
gfx_putc('?', cons_fg);
|
|
return;
|
|
}
|
|
u8_cp = (u8_cp << 6) | (c & 0x3F);
|
|
if (--u8_need > 0)
|
|
return;
|
|
int g = glyph_for(u8_cp);
|
|
gfx_putc(g ? g : '?', cons_fg);
|
|
return;
|
|
}
|
|
|
|
if (c >= 0xF0) { u8_cp = c & 0x07; u8_need = 3; return; }
|
|
else if (c >= 0xE0) { u8_cp = c & 0x0F; u8_need = 2; return; }
|
|
else if (c >= 0xC0) { u8_cp = c & 0x1F; u8_need = 1; return; }
|
|
else if (c >= 0x80) { gfx_putc('?', cons_fg); return; } // stray continuation
|
|
|
|
gfx_putc(c, cons_fg);
|
|
}
|
|
|
|
void
|
|
consputc(int c)
|
|
{
|
|
if (panicked) {
|
|
cli();
|
|
for(;;) hlt();
|
|
}
|
|
|
|
if (c == BACKSPACE) {
|
|
uartputc('\b'); uartputc(' '); uartputc('\b');
|
|
if (!cons_serial_only)
|
|
gfx_putc(c, cons_fg);
|
|
return;
|
|
}
|
|
|
|
uartputc(c);
|
|
if (!cons_serial_only)
|
|
gfx_feed(c & 0xff);
|
|
}
|
|
|
|
#define INPUT_BUF 256
|
|
struct {
|
|
struct spinlock lock;
|
|
char buf[INPUT_BUF];
|
|
uint r;
|
|
uint w;
|
|
uint e;
|
|
uint abort_read;
|
|
// Set by EOF (Ctrl-D) on an empty line. Canonical-mode end of input is not
|
|
// a character -- it is the *absence* of one -- so it cannot be signalled by
|
|
// putting something in the buffer. The old code pushed a literal ^D in and
|
|
// had consoleread() pick it back out, which meant a ^D typed mid-line was
|
|
// indistinguishable from one typed alone.
|
|
uint eof;
|
|
} input;
|
|
|
|
#define C(x) ((x)-'@')
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The line discipline
|
|
//
|
|
// TCGETS used to return a hardcoded set of flags and TCSETS threw its argument
|
|
// away, so the terminal was permanently in one mode: line-at-a-time, always
|
|
// echoing. Anything wanting a keypress at a time -- a pager, an editor, a
|
|
// shell doing its own line editing, which is most of what zsh is -- had no way
|
|
// to ask for one. This is the real thing: a struct programs can write, that
|
|
// changes what the kernel actually does.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static struct termios tio = {
|
|
.c_iflag = ICRNL,
|
|
.c_oflag = OPOST | ONLCR,
|
|
.c_cflag = CS8 | CREAD | CLOCAL,
|
|
.c_lflag = ISIG | ICANON | ECHO | ECHOE,
|
|
.c_cc = {
|
|
[VINTR] = C('C'), [VQUIT] = C('\\'), [VERASE] = '\x7f', [VKILL] = C('U'),
|
|
[VEOF] = C('D'), [VSUSP] = C('Z'), [VWERASE] = C('W'), [VMIN] = 1,
|
|
},
|
|
.c_ispeed = 15, .c_ospeed = 15,
|
|
};
|
|
|
|
void
|
|
cons_get_termios(struct termios *out)
|
|
{
|
|
acquire(&input.lock);
|
|
*out = tio;
|
|
release(&input.lock);
|
|
}
|
|
|
|
void
|
|
cons_set_termios(const struct termios *in)
|
|
{
|
|
acquire(&input.lock);
|
|
tio = *in;
|
|
// Switching out of canonical mode has to hand over whatever has been typed
|
|
// but not yet submitted, or a shell that turns off ICANON mid-line silently
|
|
// eats the characters already in the edit buffer.
|
|
if (!(tio.c_lflag & ICANON))
|
|
input.w = input.e;
|
|
release(&input.lock);
|
|
wakeup(&input.r);
|
|
}
|
|
|
|
static void
|
|
echo(int c)
|
|
{
|
|
if (c == '\n' || c == '\r') {
|
|
if ((tio.c_lflag & ECHO) || (tio.c_lflag & ECHONL))
|
|
consputc('\n');
|
|
return;
|
|
}
|
|
if (!(tio.c_lflag & ECHO))
|
|
return;
|
|
// Control characters echo as ^X, the way every other terminal shows them.
|
|
if (c < 32 && c != '\t') {
|
|
consputc('^');
|
|
consputc(c + '@');
|
|
} else {
|
|
consputc(c);
|
|
}
|
|
}
|
|
|
|
// Rub out the last character of the current line.
|
|
static void
|
|
erase_one(void)
|
|
{
|
|
if (input.e == input.w)
|
|
return;
|
|
input.e--;
|
|
if (tio.c_lflag & ECHOE)
|
|
consputc(BACKSPACE);
|
|
}
|
|
|
|
void
|
|
consoleintr(int (*getc)(void))
|
|
{
|
|
int c;
|
|
|
|
acquire(&input.lock);
|
|
while((c = getc()) >= 0){
|
|
if (c == 0)
|
|
continue;
|
|
|
|
// Input mapping, before anything looks at the character.
|
|
if (tio.c_iflag & ISTRIP) c &= 0x7f;
|
|
if (c == '\r') {
|
|
if (tio.c_iflag & IGNCR) continue;
|
|
if (tio.c_iflag & ICRNL) c = '\n';
|
|
} else if (c == '\n' && (tio.c_iflag & INLCR)) {
|
|
c = '\r';
|
|
}
|
|
|
|
// Signals. These are the characters that must not reach the reader.
|
|
//
|
|
// Ctrl-C used to do `myproc()->killed = 1` -- and `proc` in an interrupt
|
|
// handler is whatever happened to be running on this CPU when the key was
|
|
// pressed, which is almost never the program the user is looking at. It
|
|
// also set a kill flag rather than raising a signal, so nothing could
|
|
// catch it and the shell never learned the job had died. Both wrong;
|
|
// both fixed by sending a real signal to the foreground process group,
|
|
// which the kernel has been tracking in tty_fg_pgrp all along.
|
|
if (tio.c_lflag & ISIG) {
|
|
int sig = 0;
|
|
if (c == tio.c_cc[VINTR]) sig = SIGINT;
|
|
else if (c == tio.c_cc[VQUIT]) sig = SIGQUIT;
|
|
else if (c == tio.c_cc[VSUSP]) sig = SIGTSTP;
|
|
if (sig) {
|
|
echo(c);
|
|
input.e = input.w = input.r; // discard the partial line
|
|
input.abort_read = 1;
|
|
release(&input.lock);
|
|
sigsend_pgrp(tty_fg_pgrp, sig);
|
|
acquire(&input.lock);
|
|
wakeup(&input.r);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Ctrl-P: dump the process table. Not POSIX, but it is the one debugging
|
|
// aid that works when everything else has stopped responding.
|
|
if (c == C('P')) {
|
|
procdump();
|
|
continue;
|
|
}
|
|
|
|
if (tio.c_lflag & ICANON) {
|
|
if (c == tio.c_cc[VERASE] || c == C('H')) {
|
|
erase_one();
|
|
continue;
|
|
}
|
|
if (c == tio.c_cc[VKILL]) {
|
|
while (input.e != input.w &&
|
|
input.buf[(input.e-1) % INPUT_BUF] != '\n')
|
|
erase_one();
|
|
continue;
|
|
}
|
|
if (c == tio.c_cc[VWERASE]) {
|
|
while (input.e != input.w &&
|
|
input.buf[(input.e-1) % INPUT_BUF] == ' ')
|
|
erase_one();
|
|
while (input.e != input.w &&
|
|
input.buf[(input.e-1) % INPUT_BUF] != ' ')
|
|
erase_one();
|
|
continue;
|
|
}
|
|
if (c == tio.c_cc[VEOF]) {
|
|
if (input.e == input.w)
|
|
input.eof = 1; // empty line: end of input
|
|
input.w = input.e; // otherwise just submit what is there
|
|
wakeup(&input.r);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (input.e - input.r >= INPUT_BUF)
|
|
continue; // buffer full: drop it
|
|
|
|
// Everything else goes in, including bytes above 127 -- those are UTF-8
|
|
// continuation bytes, and the old `c >= 32 && c <= 126` test silently
|
|
// refused every non-ASCII character anyone tried to type.
|
|
input.buf[input.e++ % INPUT_BUF] = c;
|
|
echo(c);
|
|
|
|
// Canonical mode hands over a line at a time; raw mode hands over
|
|
// everything as it arrives.
|
|
if (!(tio.c_lflag & ICANON) ||
|
|
c == '\n' || input.e == input.r + INPUT_BUF) {
|
|
input.w = input.e;
|
|
wakeup(&input.r);
|
|
}
|
|
}
|
|
release(&input.lock);
|
|
}
|
|
|
|
int
|
|
consoleread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
uint target;
|
|
int c;
|
|
static uint32 prng_seed = 0x9A7B3C1D;
|
|
|
|
if (ip->minor == 2) {
|
|
return 0;
|
|
}
|
|
|
|
if (ip->minor == 4) {
|
|
for (int i = 0; i < n; i++) {
|
|
prng_seed ^= prng_seed << 13;
|
|
prng_seed ^= prng_seed >> 17;
|
|
prng_seed ^= prng_seed << 5;
|
|
dst[i] = prng_seed & 0xFF;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
iunlock(ip);
|
|
target = n;
|
|
acquire(&input.lock);
|
|
while(n > 0){
|
|
while(input.r == input.w){
|
|
if (input.eof) {
|
|
input.eof = 0;
|
|
release(&input.lock);
|
|
ilock(ip);
|
|
return 0; // end of input
|
|
}
|
|
if (myproc()->killed || input.abort_read) {
|
|
input.abort_read = 0;
|
|
release(&input.lock);
|
|
ilock(ip);
|
|
return -1;
|
|
}
|
|
sleep(&input.r, &input.lock);
|
|
}
|
|
c = input.buf[input.r++ % INPUT_BUF];
|
|
*dst++ = c;
|
|
--n;
|
|
|
|
// Canonical mode returns a line at a time. Raw mode returns whatever has
|
|
// arrived: a program that asked for a keypress must not be made to wait
|
|
// for a newline that is never coming.
|
|
if (tio.c_lflag & ICANON) {
|
|
if (c == '\n')
|
|
break;
|
|
} else {
|
|
if (target - n >= (uint)(tio.c_cc[VMIN] ? tio.c_cc[VMIN] : 1))
|
|
break;
|
|
}
|
|
}
|
|
release(&input.lock);
|
|
ilock(ip);
|
|
|
|
return target - n;
|
|
}
|
|
|
|
// Nonzero if a completed input line is waiting — poll(2)'s POLLIN answer
|
|
// for the console tty. Mirrors the r/w cursors consoleread() blocks on.
|
|
int
|
|
console_input_ready(void)
|
|
{
|
|
int ready;
|
|
|
|
acquire(&input.lock);
|
|
ready = input.r != input.w;
|
|
release(&input.lock);
|
|
return ready;
|
|
}
|
|
|
|
int
|
|
consolewrite(struct inode *ip, uint64 off, char *buf, int n)
|
|
{
|
|
int i;
|
|
|
|
if (ip->minor == 2 || ip->minor == 3 || ip->minor == 4) {
|
|
return n;
|
|
}
|
|
|
|
iunlock(ip);
|
|
acquire(&cons.lock);
|
|
for(i = 0; i < n; i++)
|
|
consputc(buf[i] & 0xff);
|
|
release(&cons.lock);
|
|
ilock(ip);
|
|
|
|
return n;
|
|
}
|
|
|
|
// The keyboard handler lives in kbd.c, but the console is what owns the
|
|
// keyboard as an input source, so this is where the line gets claimed.
|
|
static void
|
|
kbd_irq(void *arg)
|
|
{
|
|
(void)arg;
|
|
kbdintr();
|
|
}
|
|
|
|
void
|
|
consoleinit(void)
|
|
{
|
|
initlock(&cons.lock, "console");
|
|
initlock(&input.lock, "input");
|
|
|
|
memset(&input, 0, sizeof(input));
|
|
|
|
devsw[CONSOLE].write = consolewrite;
|
|
devsw[CONSOLE].read = consoleread;
|
|
cons.locking = 1;
|
|
|
|
irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0);
|
|
}
|