#include #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. enum { E_NORMAL, E_ESC, E_CSI }; 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) { for (int i = 0; i < esc_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; } 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 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); }