#include #include "core/types.h" #include "core/defs.h" #include "core/klog.h" #include "core/dbg.h" #include "dev/vt.h" #include "dev/kbd.h" /* KBD_VT_SWITCH -- the terminal-switch chords */ #include "gfx/gfx.h" #include "gfx/theme.h" #include "core/irq.h" #include "core/param.h" #include "arch/traps.h" #include "uapi/errno.h" #include "core/spinlock.h" #include "core/sleeplock.h" #include "fs/fs.h" #include "fs/file.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "core/proc.h" #include "uapi/posix.h" #include "arch/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. // // **That argument lost to a machine with no serial port.** A Chromebox froze at // boot showing nothing, and there was no UART to ask -- so the whole diagnosis // had to come from a photograph of a screen that had been told to stay quiet. // A log nobody can read is not a tidy boot, it is an undebuggable one. // // So kmain clears this on its first lines, as soon as the loader's framebuffer // is attached (kernel/core/main.c). What the flag still does is cover the case // where there is no framebuffer to attach: it stays set, everything goes to the // UART, and console_takeover() clears it when userspace starts. The F8 verbose // boot never set it either. 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 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]); } // Everything in the log; only what matters on the screen. // // A boot used to print about thirty lines of diagnostics to the serial line // and the console, and they are genuinely useful -- once. After that they are // the first thing you see every time you start the machine, and the message // that actually matters is buried among them. // // So they are not deleted, they are *routed*. consputc() writes to the klog // ring before anything else, so a line can be kept in full while being held // back from the two places a person is looking. `dmesg`, /proc/kmsg and the // next boot's "carried over from the previous boot" all still have it. // // Set for the duration of one kinfo() and no longer. Not a mode the system // sits in: a panic, a driver failing, anything that is not routine still goes // straight to the screen, because those are lines nobody should have to go // looking for. static int cons_logonly; static void vcprintf(char *fmt, va_list ap) { int i, c; char *s; 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; } } } void cprintf(char *fmt, ...) { va_list ap; int locking = cons.locking; va_start(ap, fmt); if (locking) acquire(&cons.lock); vcprintf(fmt, ap); gfx_flush(); if (locking) release(&cons.lock); va_end(ap); // The kernel's own output reaches the second display too, and for the same // reason it is done here rather than in consputc(): outside the lock. On a // quiet boot almost nothing comes through this path -- kinfo() goes to the // log -- so this is mostly for a verbose boot, where it is the whole point. if (locking) display_mirror_console(); } // A line for the log, and for the screen only on a verbose boot. // // The rule for choosing between this and cprintf is not "how interesting is // it" -- everything here was interesting to somebody once. It is: **would you // want to see this on a machine that is working?** `pci: 7 devices` is a yes // the first time and a no every time after. `e1000: no memory for its rings` // is a yes for ever, and stays a cprintf. void kinfo(char *fmt, ...) { va_list ap; int locking = cons.locking; va_start(ap, fmt); if (locking) acquire(&cons.lock); if (!boot_verbose) cons_logonly++; vcprintf(fmt, ap); if (!boot_verbose) cons_logonly--; gfx_flush(); if (locking) release(&cons.lock); va_end(ap); } // 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(); } #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. // --------------------------------------------------------------------------- // Output goes to the terminal layer now. The escape parser, the palette, the // cursor and the UTF-8 decoder all moved to kernel/dev/vt.c, because each of // them is per-terminal and there used to be exactly one of each. void consputc(int c) { if (panicked) { cli(); for(;;) hlt(); } // Every kernel message passes through here, which is what makes this the // one hook the log needs: cprintf, panic's report, a driver's complaint at // three in the morning. See include/core/klog.h. klog_putc(c); // Held back from the screen and the wire, kept in the log. See kinfo(). if (cons_logonly) return; // The kernel's own erase, echoed when you press backspace at a prompt the // line discipline is reading. Now that the terminal's `\b` only moves -- see // the note in vt.c -- this has to spell the erase out, exactly as it already // did for the serial line. if (c == BACKSPACE) { uartputc('\b'); uartputc(' '); uartputc('\b'); if (!cons_serial_only) { struct vt *v = vt_current(); vt_putc(v, '\b'); vt_putc(v, ' '); vt_putc(v, '\b'); } return; } uartputc(c); if (!cons_serial_only) vt_feed(vt_current(), c & 0xff); } #define C(x) ((x)-'@') // --------------------------------------------------------------------------- // Terminals as devices // // The line discipline below used to operate on one `struct input` and one // `struct termios`, because there was one terminal. Both live in struct vt // now (kernel/dev/vt.c) and everything here takes the terminal it is acting // on as an argument. // // One lock still guards every terminal's input rather than one lock each. // The rings are only touched from the keyboard interrupt and from read(2), // both of which are short, and a single lock cannot be acquired in the wrong // order against itself. Per-terminal locks would buy nothing until there is a // second input source to contend with it. // --------------------------------------------------------------------------- static struct spinlock input_lock; // Which terminal a device minor names. // // 1 /dev/console the active terminal, so kernel messages and a // shell started on the console both follow a switch // 10 /dev/tty the caller's controlling terminal // 11..16 /dev/tty1..6 that terminal, whether or not it is on screen // // Anything else is the console, which is what an unrecognised minor meant // before there was more than one terminal to get wrong. struct vt * vt_for_minor(int minor) { if (minor >= 11 && minor < 11 + NVT) return vt_get(minor - 11); if (minor == 10) { struct proc *p = myproc(); if (p && p->ctty > 0 && p->ctty <= NVT) return vt_get(p->ctty - 1); } return vt_current(); } // One output byte to a terminal. // // The serial line only hears from the terminal that is on screen. Two // terminals interleaving into one serial log would be unreadable, and the // serial console is meant to show what the monitor shows. static void vt_out(struct vt *v, int c) { if (v == vt_current()) uartputc(c); if (!cons_serial_only) vt_feed(v, c & 0xff); } void cons_get_termios(int minor, struct termios *out) { struct vt *v = vt_for_minor(minor); acquire(&input_lock); *out = v->tio; release(&input_lock); } void cons_set_termios(int minor, const struct termios *in) { struct vt *v = vt_for_minor(minor); acquire(&input_lock); v->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 (!(v->tio.c_lflag & ICANON)) v->in.w = v->in.e; release(&input_lock); wakeup(&v->in.r); } // Discard everything typed but not yet consumed: completed lines waiting in // the buffer and the partial line still being edited. TCFLSH (tcflush) is the // caller, and a line editor issues one whenever it takes the terminal back -- // after an interrupt, or when a command that read from the tty exits. Without // it the keys pressed while the last command ran arrive at the next prompt. // // A pending EOF goes too: end of input is buffer state like any other, and // leaving it set would end the *next* read instead of the one it was typed at. void cons_flush_input(int minor) { struct vt *v = vt_for_minor(minor); acquire(&input_lock); v->in.e = v->in.w = v->in.r; v->in.eof = 0; release(&input_lock); } // Foreground process group, per terminal. This was one global int for the // whole machine; tcsetpgrp on tty2 now says nothing about tty1, which is the // entire point of having more than one. int cons_fg_pgrp(int minor) { return vt_for_minor(minor)->fg_pgrp; } void cons_set_fg_pgrp(int minor, int pg) { vt_for_minor(minor)->fg_pgrp = pg; } static void echo(struct vt *v, int c) { if (c == '\n' || c == '\r') { if ((v->tio.c_lflag & ECHO) || (v->tio.c_lflag & ECHONL)) vt_out(v, '\n'); return; } if (!(v->tio.c_lflag & ECHO)) return; // Control characters echo as ^X, the way every other terminal shows them. if (c < 32 && c != '\t') { vt_out(v, '^'); vt_out(v, c + '@'); } else { vt_out(v, c); } } // Rub out the last character of the current line. static void erase_one(struct vt *v) { if (v->in.e == v->in.w) return; v->in.e--; if (v->tio.c_lflag & ECHOE) { if (v == vt_current()) { uartputc('\b'); uartputc(' '); uartputc('\b'); } if (!cons_serial_only) vt_putc(v, '\b'); } } // --------------------------------------------------------------------------- // Keyboard input // --------------------------------------------------------------------------- // Ctrl+Alt+Shift+F1..F6 switch terminals. kbd.c reports them as // KBD_VT_SWITCH+n so that the // decision of what a chord *means* is made here, where the terminals are, // rather than in a driver that should not know they exist. int console_vt_hotkey(int key) { if (key != KBD_VT_PREV && key != KBD_VT_NEXT && (key < KBD_VT_SWITCH || key >= KBD_VT_SWITCH + NVT)) return 0; // A program that called VT_SETMODE(VT_PROCESS) owns switching on this // terminal. Dropping the chord entirely would be worse than not binding it: // it would vanish instead of reaching the program that asked for it. So it // falls through to the line discipline as an ordinary key. if (!vt_kernel_switches()) return 0; if (key == KBD_VT_PREV) vt_cycle(-1); else if (key == KBD_VT_NEXT) vt_cycle(+1); else vt_switch(key - KBD_VT_SWITCH); return 1; } void consoleintr(int (*getc)(void)) { int c; acquire(&input_lock); while((c = getc()) >= 0){ if (c == 0) continue; // Terminal switching happens before the line discipline sees anything: // the chord is not input for the terminal you are leaving. if (c == KBD_VT_PREV || c == KBD_VT_NEXT || (c >= KBD_VT_SWITCH && c < KBD_VT_SWITCH + NVT)) { release(&input_lock); int taken = console_vt_hotkey(c); acquire(&input_lock); if (taken) continue; continue; /* VT_PROCESS: nothing sensible to hand the tty yet */ } // Input goes to whichever terminal is on screen. That is what makes a // terminal a terminal: the keyboard follows the eyes. struct vt *v = vt_current(); if (!vt_takes_keys(v)) continue; // K_OFF: its owner reads /dev/input // Input mapping, before anything looks at the character. ISTRIP is about // seven-bit lines and applies to characters only -- masking a keycode // would turn an arrow key into whatever letter its low seven bits spell. if ((v->tio.c_iflag & ISTRIP) && c < KBD_SPECIAL) c &= 0x7f; if (c == '\r') { if (v->tio.c_iflag & IGNCR) continue; if (v->tio.c_iflag & ICRNL) c = '\n'; } else if (c == '\n' && (v->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 // is now this terminal's group rather than the machine's only one. if (v->tio.c_lflag & ISIG) { int sig = 0; if (c == v->tio.c_cc[VINTR]) sig = SIGINT; else if (c == v->tio.c_cc[VQUIT]) sig = SIGQUIT; else if (c == v->tio.c_cc[VSUSP]) sig = SIGTSTP; if (sig) { echo(v, c); v->in.e = v->in.w = v->in.r; // discard the partial line v->in.abort_read = 1; release(&input_lock); sigsend_pgrp(v->fg_pgrp, sig); acquire(&input_lock); wakeup(&v->in.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; } // Ctrl-G: every debug view, on the console. // // The same thing `sysrq g` and `schdbg all` produce, reachable with no // filesystem mounted and no shell running -- which is the point. It only // asks; a kernel thread does the work, because the views take locks and // this is an interrupt handler. See dbg_request_dump(). if (c == C('G')) { release(&input_lock); dbg_request_dump(); acquire(&input_lock); continue; } if (v->tio.c_lflag & ICANON) { if (c == v->tio.c_cc[VERASE] || c == C('H')) { erase_one(v); continue; } if (c == v->tio.c_cc[VKILL]) { while (v->in.e != v->in.w && v->in.buf[(v->in.e-1) % VT_INPUT_BUF] != '\n') erase_one(v); continue; } if (c == v->tio.c_cc[VWERASE]) { while (v->in.e != v->in.w && v->in.buf[(v->in.e-1) % VT_INPUT_BUF] == ' ') erase_one(v); while (v->in.e != v->in.w && v->in.buf[(v->in.e-1) % VT_INPUT_BUF] != ' ') erase_one(v); continue; } if (c == v->tio.c_cc[VEOF]) { if (v->in.e == v->in.w) v->in.eof = 1; // empty line: end of input v->in.w = v->in.e; // otherwise submit what is there wakeup(&v->in.r); continue; } } // Arrow keys and the rest of the navigation block, as escape sequences. // // These arrive as single keycodes and used to be pushed into the input // buffer exactly like that. A terminal has never worked that way, and the // values chosen made it worse: KBD_UP was 0xE2, a UTF-8 lead byte for a // three-byte character, so a reader decoding UTF-8 swallowed the next two // keys waiting for continuations that never came -- every arrow key // corrupted the two keystrokes after it. The keycodes now sit above // U+10FFFF where nothing can collide with them; see include/dev/kbd.h. // // What a terminal sends is `ESC [ A`, and what makes zsh's line editor // work is receiving that. The sequences below are the DEC/xterm ones that // every terminfo entry and every line editor already knows. { const char *esc = 0; switch (c) { case KBD_UP: esc = "\033[A"; break; case KBD_DN: esc = "\033[B"; break; case KBD_RT: esc = "\033[C"; break; case KBD_LF: esc = "\033[D"; break; case KBD_HOME: esc = "\033[H"; break; case KBD_END: esc = "\033[F"; break; case KBD_PGUP: esc = "\033[5~"; break; case KBD_PGDN: esc = "\033[6~"; break; case KBD_INS: esc = "\033[2~"; break; case KBD_DEL: esc = "\033[3~"; break; } if (esc) { // Not echoed. In raw mode -- which is where a line editor lives, and // the only mode where these mean anything -- nothing is echoed anyway. // In canonical mode echoing them would send the escape straight back // to the renderer and move the cursor for real, which is the other // half of what "weird glitches" looked like. for (const char *q = esc; *q; q++) { if (v->in.e - v->in.r >= VT_INPUT_BUF) break; // buffer full: drop the rest v->in.buf[v->in.e++ % VT_INPUT_BUF] = *q; } if (!(v->tio.c_lflag & ICANON)) { v->in.w = v->in.e; wakeup(&v->in.r); } continue; } } // Any keycode still here is a special nobody translated. Dropping it is // the only safe thing: storing it would truncate to a byte and type a // character the user did not press. if (c >= KBD_SPECIAL) continue; if (v->in.e - v->in.r >= VT_INPUT_BUF) continue; // buffer full: drop it // Everything else goes in, including bytes above 127 -- those are UTF-8, // arriving a byte at a time from the serial line or encoded by kbdgetc, // and the old `c >= 32 && c <= 126` test silently refused every non-ASCII // character anyone tried to type. v->in.buf[v->in.e++ % VT_INPUT_BUF] = c; echo(v, c); // Canonical mode hands over a line at a time; raw mode hands over // everything as it arrives. if (!(v->tio.c_lflag & ICANON) || c == '\n' || v->in.e == v->in.r + VT_INPUT_BUF) { v->in.w = v->in.e; wakeup(&v->in.r); } } release(&input_lock); gfx_flush(); // the echo } 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; } struct vt *v = vt_for_minor(ip->minor); iunlock(ip); target = n; acquire(&input_lock); while(n > 0){ while(v->in.r == v->in.w){ if (v->in.eof) { v->in.eof = 0; release(&input_lock); ilock(ip); return 0; // end of input } // Interrupted, not forbidden. // // This returned -1, and -1 is -EPERM. A read broken off by Ctrl-C // therefore reached userspace as "operation not permitted", which is a // permanent error -- so every program that handles SIGINT by carrying on // saw a fatal one instead and exited: // // zsh: error on TTY read: operation not permitted // schinitd: restarting os.schos.console-shell // // and then did it again, because the next Ctrl-C did the same thing. A // shell that cannot survive Ctrl-C is a shell nobody can use. // // EINTR is the answer a signal-interrupted read is required to give, and // the whole convention -- retry, or return to the caller -- is built on // getting it rather than something else. if (myproc()->killed || v->in.abort_read) { v->in.abort_read = 0; release(&input_lock); ilock(ip); return -EINTR; } sleep(&v->in.r, &input_lock); } c = v->in.buf[v->in.r++ % VT_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 (v->tio.c_lflag & ICANON) { if (c == '\n') break; } else { if (target - n >= (uint)(v->tio.c_cc[VMIN] ? v->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 // a terminal. Mirrors the r/w cursors consoleread() blocks on. int console_input_ready(int minor) { struct vt *v = vt_for_minor(minor); int ready; acquire(&input_lock); ready = v->in.r != v->in.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; struct vt *v = vt_for_minor(ip->minor); iunlock(ip); acquire(&cons.lock); if (panicked) { release(&cons.lock); ilock(ip); cli(); for(;;) hlt(); } for(i = 0; i < n; i++) vt_out(v, buf[i] & 0xff); gfx_flush(); release(&cons.lock); // And onto a second display, if the kernel drives one. // // **After the lock, not inside it.** A display driver may have something to // say -- vga_disp_acquire() announces its back buffer, the virtio driver // complains when the device does not answer -- and cprintf() from inside // this lock is a CPU acquiring a lock it already holds, which is a panic // with a very confusing call chain. Found exactly that way. // // Once per write() rather than once per character, which is also the better // batching: a line of output is one transfer instead of eighty. display_mirror_console(); ilock(ip); return n; } // A descriptor has been opened on a terminal, or the last one closed. // // This is the only thing that knows a terminal is in use, which is what // VT_GETSTATE reports through v_state and what VT_OPENQRY looks for when it // is asked for a free one. Without it `chvt` says every terminal is idle even // while two shells are sitting on them. static void consoleopen(struct inode *ip) { if (ip->minor >= 11 && ip->minor < 11 + NVT) vt_get(ip->minor - 11)->opened++; } static void consoleclose(struct inode *ip) { if (ip->minor >= 11 && ip->minor < 11 + NVT) { struct vt *v = vt_get(ip->minor - 11); if (v->opened > 0) v->opened--; } } // 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. // A keyboard byte in the controller, which IRQ 1 should have announced. 0xFF // is no controller at all: every status bit set, the auxiliary one included, // so it does not count. static int kbd_waiting(void) { uchar st = inb(KBSTATP); return st != 0xFF && (st & 0x21) == 0x01; } static void kbd_irq(void *arg) { (void)arg; kbdintr(); } void consoleinit(void) { initlock(&cons.lock, "console"); initlock(&input_lock, "input"); // Not read and write. Major 1 is the whole mem major -- null, zero, full, // random, urandom as well as the console and the ttys -- and devsw[1] is // memread/memwrite, which send the console's minors here and the rest // where they belong. Installing consoleread over it made /dev/zero and // /dev/urandom wait for a line from the keyboard, which is why neither // had ever seemed to work. devsw[CONSOLE].open = consoleopen; devsw[CONSOLE].close = consoleclose; cons.locking = 1; irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0); irq_watch(IRQ_KBD, kbd_waiting); }