do not edit — generated by btf.
git.druid.rocksindexdruid520kaboomsrc/drivers/serial.nsc

src/drivers/serial.nsc


/*
 * com1 (0x3f8) 16550 uart driver. started as a test/debug output
 * channel only (boot.s already proved the pipeline got this far with
 * one raw outb before kmain even ran) -- now also a real INPUT
 * channel (serial_handle_irq, below), because -nographic makes it
 * the ONLY channel: there's no separate graphical window with its
 * own ps/2-backed keyboard focus, just one real terminal whose
 * keystrokes arrive as raw bytes over this same uart, not as ps/2
 * scancodes. found for real: every keystroke this whole project's
 * testing ever used was qemu's own QMP send-key, which injects real
 * ps/2 scancodes regardless of display mode -- a real user typing
 * into a real -nographic session was never actually exercised until
 * someone tried it and nothing happened at all, because kbd.nsc's
 * irq1 handler has nothing to fire for a byte that arrived over com1.
 */
 
void outb(u16 port, u8 val);
u8 inb(u16 port);
void kbd_push(u8 ch);
 
global void
serial_init(void)
{
	outb((u16)0x3f9, (u8)0x00);
	outb((u16)0x3fb, (u8)0x80);
	outb((u16)0x3f8, (u8)0x03);
	outb((u16)0x3f9, (u8)0x00);
	outb((u16)0x3fb, (u8)0x03);
	outb((u16)0x3fa, (u8)0xc7);
	outb((u16)0x3fc, (u8)0x0b);
	/* enable the uart's own "received data available" interrupt
	 * (interrupt enable register, bit 0) -- without this the uart
	 * never asserts irq4 at all, no matter how many bytes arrive. */
	outb((u16)0x3f9, (u8)0x01);
}
 
u8
serial_tx_ready(void)
{
	u8 status;
	status = inb((u16)0x3fd);
	return status & (u8)0x20;
}
 
/* translates a bare '\n' to "\r\n" -- qemu's -nographic puts the
 * host terminal itself into raw mode so keystrokes pass straight
 * through to the guest, which also means the terminal's own output
 * side does none of its usual \n->\r\n translation (that's normally
 * termios's job, disabled along with everything else raw mode turns
 * off): a real serial device is expected to send both bytes itself.
 * without this, a bare \n just moves down a row without returning to
 * column 0, so anything with more than one line (cat'ing any file
 * more than a few words long) walks the cursor right with every
 * line and leaves the next prompt printed mid-line, looking exactly
 * like a broken cursor -- found by actually running kaboom over
 * -nographic and cat'ing a multi-line file, not from reading the
 * code (a captured-to-a-file serial log, this kernel's only test
 * method until now, never shows this: the file just accumulates
 * bytes with no live terminal cursor to confuse). doing this once,
 * here, fixes every caller (serial_puts, fd_write's console output,
 * the keyboard echo) instead of needing the same fix repeated at
 * each one. */
global void
serial_putc(u8 ch)
{
	if(ch == (u8)10)
	{
		while(serial_tx_ready() == (u8)0)
		{
		}
		outb((u16)0x3f8, (u8)13);
	}
	while(serial_tx_ready() == (u8)0)
	{
	}
	outb((u16)0x3f8, ch);
}
 
global void
serial_puts(ptr str)
{
	ptr p;
	u64 word;
	u8 ch;
 
	p = str;
	while(1)
	{
		word = (u64)*p;
		ch = (u8)(word & (u64)0xff);
		if(ch == (u8)0)
		{
			return;
		}
		serial_putc(ch);
		p = p + 1;
	}
}
 
/* clears whatever real terminal is on the other end of com1, via the
 * same ansi "clear screen + cursor home" sequence any ordinary
 * terminal-based `clear` sends -- found missing by testing the clear
 * command over -nographic specifically: vga_clear (vga.nsc) only ever
 * touched the vga hardware framebuffer, which -nographic has no
 * window onto at all, so the serial-only console it actually gives a
 * real user just... never got cleared, with nothing sent over the
 * one channel that mattered. */
global void
serial_clear(void)
{
	serial_puts("\x1b[2J\x1b[H");
}
 
u8
serial_rx_ready(void)
{
	u8 status;
	status = inb((u16)0x3fd);
	return status & (u8)0x01;
}
 
/* irq4 (com1): a byte arrived from whatever real terminal is
 * actually talking to this machine. two translations happen here,
 * neither of which a real ps/2 keystroke ever needed, both of which
 * a raw-mode host terminal needs done FOR it since raw mode is
 * exactly "the terminal does none of its usual translation, the far
 * end is on its own":
 *
 * - enter usually arrives as a bare '\r' (13), not '\n' (10) --
 *   fd_read_stdin's own end-of-line check is ch==10 only, so without
 *   this a typed line would simply never be seen as finished.
 * - backspace can arrive as either the "backspace" byte (8, what a
 *   ps/2 keystroke already produces) or "delete" (127, what a lot of
 *   real terminals actually send for the same physical key) --
 *   folded to 8 so fd_read_stdin's existing ch==8 handling (see its
 *   own note on why backspace's real erase logic lives there, not in
 *   an irq handler) covers both without needing to know two byte
 *   values mean the same key.
 *
 * pushed through the exact same kbd_push kbd.nsc's own irq1 handler
 * uses -- one shared buffer, one shared echo rule, regardless of
 * which physical path a byte actually arrived through. */
global void
serial_handle_irq(void)
{
	u8 ch;
 
	if(serial_rx_ready() == (u8)0)
	{
		return;
	}
	ch = inb((u16)0x3f8);
 
	if(ch == (u8)13)
	{
		ch = (u8)10;
	}
	else if(ch == (u8)127)
	{
		ch = (u8)8;
	}
 
	/* drop anything else outside the handful of control bytes this
	 * console actually understands (8 backspace, 9 tab, 10 newline)
	 * -- found for real by testing arrow keys and the delete key over
	 * -nographic: both start with esc (27), which fd_read_stdin has
	 * no concept of at all, so it always went into the line buffer
	 * (and got echoed back) as an ordinary character. echoing esc back
	 * is the actual problem, not just cosmetic: the user's REAL
	 * terminal on the other end sees an escaped byte sequence coming
	 * FROM kaboom (indistinguishable from one of kaboom's own real
	 * ansi sequences, like serial_clear's) and faithfully executes it
	 * as a genuine cursor move, letting them navigate up into and
	 * "edit" output that's already scrolled past -- purely a trick of
	 * the real terminal's own rendering, kaboom's own line buffer was
	 * never actually touched by any of it. dropping esc (and every
	 * other unhandled control byte) before it's ever echoed or pushed
	 * closes that off entirely; whatever printable bytes followed
	 * (arrow keys are esc+[+A/B/C/D) still come through afterward as
	 * plain, harmless characters with no leading esc to give them any
	 * special meaning. */
	if(ch != (u8)10 && ch != (u8)8 && ch != (u8)9 && (ch < (u8)32 || ch > (u8)126))
	{
		return;
	}
 
	kbd_push(ch);
}
powered by btf.