| git.druid.rocks | index | druid520 | kaboom | src/ | drivers/ | vga.nsc |
src/drivers/vga.nsc
/*
* text-mode vga driver: 0xb8000, 80x25, 2 bytes/cell (char, attr).
*
* nsc's `*p` through a raw ptr is always a full 8-byte load/store --
* there is no byte or word-granular deref, and structs can't model a
* packed hardware layout either (fields are word-addressed, not
* byte-packed). every single-cell write here is therefore a
* read-modify-write of the containing 8-byte-aligned word: read 8
* bytes, clear the 2 bytes for this cell with a mask, or in the new
* (char | attr<<8) value at the right bit offset, write the 8 bytes
* back. see the kernel-wide note on this in kernel/kmain.nsc.
*
* cursor positioning (vga_set_cursor/vga_show_cursor/vga_hide_cursor)
* and real scrolling (vga_scroll) are a port of tape-kernel's own
* cm.c (cnb/cob/scur/hcur) and vga.c (scrl) -- not reinvented. the
* one thing not ported: cnb (reading the cursor position back from
* the crtc) has no caller here, since vga_row/vga_col already track
* kaboom's own idea of "where the cursor is" and the two never
* diverge (nothing else ever moves the hardware cursor independently
* of a vga_putc/vga_newline call) -- cob's counterpart, cnb's actual
* job, would only matter if something else could move the cursor
* without going through this file.
*/
void outb(u16 port, u8 val);
u8 inb(u16 port);
global u32 vga_row;
global u32 vga_col;
void
vga_setcell(u32 row, u32 col, u8 ch, u8 attr)
{
ptr base;
ptr wordptr;
u64 celloff;
u64 wordoff;
u64 byteoff;
u64 shift;
u64 word;
u64 mask;
u64 val;
base = (ptr)0xb8000;
celloff = (u64)(row * (u32)80 + col) * (u64)2;
wordoff = celloff & ~(u64)7;
byteoff = celloff & (u64)7;
shift = byteoff * (u64)8;
wordptr = base + wordoff;
word = (u64)*wordptr;
val = (u64)ch | ((u64)attr << (u64)8);
mask = (u64)0xffff << shift;
word = (word & ~mask) | (val << shift);
*wordptr = (i64)word;
}
/* the (char|attr) pair packed the same way vga_setcell writes it --
* scrolling needs to read a cell before overwriting it with the row
* below's own content. */
u64
vga_getcell(u32 row, u32 col)
{
ptr base;
ptr wordptr;
u64 celloff;
u64 wordoff;
u64 byteoff;
u64 shift;
u64 word;
base = (ptr)0xb8000;
celloff = (u64)(row * (u32)80 + col) * (u64)2;
wordoff = celloff & ~(u64)7;
byteoff = celloff & (u64)7;
shift = byteoff * (u64)8;
wordptr = base + wordoff;
word = (u64)*wordptr;
return (word >> shift) & (u64)0xffff;
}
/* sets the hardware text cursor's screen position (crtc index
* registers 0x0e/0x0f) -- tape-kernel's cob, ported directly (same
* clamping, same pos = y*80+x encoding). */
global void
vga_set_cursor(u32 row, u32 col)
{
u64 pos;
if(row >= (u32)25)
{
row = (u32)24;
}
if(col >= (u32)80)
{
col = (u32)79;
}
pos = (u64)(row * (u32)80 + col);
outb((u16)0x3d4, (u8)0x0f);
outb((u16)0x3d5, (u8)(pos & (u64)0xff));
outb((u16)0x3d4, (u8)0x0e);
outb((u16)0x3d5, (u8)((pos >> (u64)8) & (u64)0xff));
}
/* enables the blinking text cursor at a normal underline shape --
* tape-kernel's scur. */
global void
vga_show_cursor(void)
{
outb((u16)0x3d4, (u8)0x0a);
outb((u16)0x3d5, (u8)0x06);
outb((u16)0x3d4, (u8)0x0b);
outb((u16)0x3d5, (u8)0x07);
}
/* tape-kernel's hcur -- bit 5 of the cursor-start register disables
* the cursor entirely. not called anywhere yet (vga_init always
* shows it), kept for symmetry and whatever wants it later. */
global void
vga_hide_cursor(void)
{
outb((u16)0x3d4, (u8)0x0a);
outb((u16)0x3d5, (u8)0x20);
}
/* shifts every row up by one and clears the new bottom row --
* tape-kernel's scrl, ported directly. called instead of wrapping
* back to row 0 the moment output reaches the bottom, so the screen
* behaves like a real terminal (oldest line scrolls off, newest
* stays visible) instead of overwriting from the top over whatever
* was already there. */
void
vga_scroll(void)
{
u32 row;
u32 col;
u64 cell;
row = 1;
while(row < (u32)25)
{
col = 0;
while(col < (u32)80)
{
cell = vga_getcell(row, col);
vga_setcell(row - (u32)1, col, (u8)(cell & (u64)0xff), (u8)((cell >> (u64)8) & (u64)0xff));
col = col + (u32)1;
}
row = row + (u32)1;
}
col = 0;
while(col < (u32)80)
{
vga_setcell((u32)24, col, (u8)32, (u8)7);
col = col + (u32)1;
}
}
void
vga_newline(void)
{
vga_row = vga_row + (u32)1;
vga_col = 0;
if(vga_row >= (u32)25)
{
vga_scroll();
vga_row = (u32)24;
}
vga_set_cursor(vga_row, vga_col);
}
global void
vga_clear(void)
{
u32 row;
u32 col;
row = 0;
while(row < (u32)25)
{
col = 0;
while(col < (u32)80)
{
vga_setcell(row, col, (u8)32, (u8)7);
col = col + (u32)1;
}
row = row + (u32)1;
}
vga_row = 0;
vga_col = 0;
vga_set_cursor(0, 0);
}
global void
vga_putc(u8 ch)
{
if(ch == (u8)10)
{
vga_newline();
return;
}
vga_setcell(vga_row, vga_col, ch, (u8)7);
vga_col = vga_col + (u32)1;
if(vga_col >= (u32)80)
{
vga_newline();
return;
}
vga_set_cursor(vga_row, vga_col);
}
/* erases the character immediately before the cursor and moves the
* cursor back onto it -- fd_read_stdin's own backspace handling
* calls this once it's confirmed there's something on the current
* line to erase (see that function's note on why the decision itself
* lives there, not in the keyboard driver). backing up across a
* wrapped line (col already 0) moves to the end of the previous row;
* this is an approximation, not a real "how long was that row"
* tracker, but matches the common case (a line that filled the
* screen width) well enough for a line editor's own input. */
global void
vga_backspace(void)
{
if(vga_col > (u32)0)
{
vga_col = vga_col - (u32)1;
}
else if(vga_row > (u32)0)
{
vga_row = vga_row - (u32)1;
vga_col = (u32)79;
}
vga_setcell(vga_row, vga_col, (u8)32, (u8)7);
vga_set_cursor(vga_row, vga_col);
}
global void
vga_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;
}
vga_putc(ch);
p = p + 1;
}
}
global void
vga_init(void)
{
vga_row = 0;
vga_col = 0;
vga_clear();
vga_show_cursor();
}