Two things, both about the debugger being available at the moment you
actually need it rather than once a shell exists.
**Ctrl-G on the console runs every view.** No filesystem, no /dev, no
shell -- it is the last thing main() sets up before entering the scheduler,
so it answers from early in boot, which is exactly when boot problems
happen. It sits beside the older Ctrl-P, and the difference between them is
the interesting part: procdump() takes no lock at all, deliberately,
because a lock is a way to hang while reporting a hang. The views cannot
follow that rule -- a free list read torn is worse than not read -- so they
take locks, and a lock must never be taken from an interrupt handler that
may have interrupted its holder. The key therefore *requests* and a kernel
thread *performs*. A scheduler too wedged to run a thread will not answer
Ctrl-G; Ctrl-P still will, and that division is the honest one.
**PSOGM says where the report went.** Its closing line used to be an
addr2line command, which the call chain no longer needs -- it prints names
now. What replaced it is the thing nobody would guess:
This report is in /proc/kmsg after a restart.
schdbg, or Ctrl-G on the console, asks more.
PSOGM -- Purple Screen of Guru Meditation
"Restart and read it" beats "photograph this screen" by a wide margin, and
until the log outlived the kernel it was not true. Both panic.c and
graphics.md had claimed in writing that the screen was the only artefact of
a crash; neither says so now. The QR code stays, because it is still the
only channel that works when the machine will not come back at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
675 lines
19 KiB
C
675 lines
19 KiB
C
#include <stdarg.h>
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "klog.h"
|
|
#include "dbg.h"
|
|
#include "vt.h"
|
|
#include "kbd.h" /* KEY_VT_SWITCH -- the terminal-switch chords */
|
|
#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.
|
|
//
|
|
// **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]);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
|
|
#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/klog.h.
|
|
klog_putc(c);
|
|
|
|
if (c == BACKSPACE) {
|
|
uartputc('\b'); uartputc(' '); uartputc('\b');
|
|
if (!cons_serial_only)
|
|
vt_putc(vt_current(), '\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
|
|
// KEY_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 != KEY_VT_PREV && key != KEY_VT_NEXT &&
|
|
(key < KEY_VT_SWITCH || key >= KEY_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 == KEY_VT_PREV) vt_cycle(-1);
|
|
else if (key == KEY_VT_NEXT) vt_cycle(+1);
|
|
else vt_switch(key - KEY_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 == KEY_VT_PREV || c == KEY_VT_NEXT ||
|
|
(c >= KEY_VT_SWITCH && c < KEY_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();
|
|
|
|
// Input mapping, before anything looks at the character.
|
|
if (v->tio.c_iflag & ISTRIP) 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;
|
|
}
|
|
}
|
|
|
|
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
|
|
// continuation bytes, 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);
|
|
}
|
|
|
|
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
|
|
}
|
|
if (myproc()->killed || v->in.abort_read) {
|
|
v->in.abort_read = 0;
|
|
release(&input_lock);
|
|
ilock(ip);
|
|
return -1;
|
|
}
|
|
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);
|
|
release(&cons.lock);
|
|
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.
|
|
static void
|
|
kbd_irq(void *arg)
|
|
{
|
|
(void)arg;
|
|
kbdintr();
|
|
}
|
|
|
|
void
|
|
consoleinit(void)
|
|
{
|
|
initlock(&cons.lock, "console");
|
|
initlock(&input_lock, "input");
|
|
|
|
devsw[CONSOLE].write = consolewrite;
|
|
devsw[CONSOLE].read = consoleread;
|
|
devsw[CONSOLE].open = consoleopen;
|
|
devsw[CONSOLE].close = consoleclose;
|
|
cons.locking = 1;
|
|
|
|
irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0);
|
|
}
|