217 lines
6.1 KiB
C
217 lines
6.1 KiB
C
// Implementation of a simple text-only VGA driver
|
|
#include "screen.h"
|
|
#include "kernel/ktypes.h"
|
|
|
|
|
|
i32 getCursorOffset(void);
|
|
void setCursorOffset(i32 offset);
|
|
i32 putchar(byte ch, i32 col, i32 row, byte attr);
|
|
i32 getOffset(i32 col, i32 row);
|
|
i32 getRow(i32 offset);
|
|
i32 getColumn(i32 offset);
|
|
|
|
|
|
// Public API below
|
|
|
|
void kprintAt(byte* message, i32 col, i32 row) {
|
|
/*
|
|
Prints a null-terminated string to the VGA
|
|
text buffer at the specified row and column.
|
|
The cursor's position is not updated. If both
|
|
col and row are negative, the current cursor
|
|
location is used
|
|
*/
|
|
i32 offset;
|
|
if (col >= 0 && row >= 0) {
|
|
offset = getOffset(col, row);
|
|
}
|
|
else {
|
|
offset = getCursorOffset();
|
|
row = getRow(offset);
|
|
col = getColumn(offset);
|
|
}
|
|
for (i32 i = 0; message[i]; i++) {
|
|
offset = putchar(message[i], col, row, 0);
|
|
// We compute the new row and column for the
|
|
// next iteration. Hopefully the compiler inlines
|
|
// these two calls
|
|
row = getRow(offset);
|
|
col = getColumn(offset);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void kprint(byte* message) {
|
|
/*
|
|
Prints a null-terminated string to the
|
|
VGA text buffer
|
|
*/
|
|
kprintAt(message, -1, -1);
|
|
}
|
|
|
|
|
|
// Private API below
|
|
|
|
i32 putchar(byte ch, i32 col, i32 row, byte attr) {
|
|
/*
|
|
Writes a single character to the VGA text buffer
|
|
at the given row and column and returns the new
|
|
location of the cursor. If the attr byte is zero,
|
|
its value is the result of the expansion of the
|
|
LIGHT_GREY_ON_BLACK macro. If both row and col
|
|
are equal to -1, the cursor location is retrieved
|
|
from the VGA controller using I/O ports. The following
|
|
escape sequences are handled:
|
|
- '\n' -> Goes to the next line
|
|
- '\t' -> Prints VGA_TABSIZE spaces
|
|
- '\r' -> Resets the cursor's column, but not the row
|
|
*/
|
|
|
|
if (!attr) {
|
|
// No color data provided? We supply our own
|
|
attr = LIGHT_GREY_ON_BLACK;
|
|
}
|
|
i32 offset = (col >= 0 && row >= 0)? getOffset(col, row): getCursorOffset();
|
|
switch (ch) {
|
|
// Note the difference between a carriage return (which
|
|
// only brings the write head back at 0 without changing
|
|
// the row) and the newline (which increases the row as well)
|
|
case '\r':
|
|
offset = getOffset(0, getRow(offset));
|
|
break;
|
|
case '\n':
|
|
offset = getOffset(0, getRow(offset) + 1);
|
|
break;
|
|
case '\t':
|
|
for (i32 i = 0; i < VGA_TABSIZE; i++) {
|
|
VMEM_BUF[offset] = ' ';
|
|
VMEM_BUF[offset + 1] = attr;
|
|
offset += 2;
|
|
}
|
|
break;
|
|
default:
|
|
VMEM_BUF[offset] = ch;
|
|
VMEM_BUF[offset + 1] = attr;
|
|
offset += 2;
|
|
break;
|
|
}
|
|
setCursorOffset(offset);
|
|
return offset;
|
|
}
|
|
|
|
|
|
i32 getCursorOffset(void) {
|
|
/*
|
|
Returns the current offset of
|
|
the VGA text cursor
|
|
*/
|
|
|
|
// We request the high byte of the
|
|
// cursor position by writing to the
|
|
// VGA control port. We can then read
|
|
// the result on the VGA data port.
|
|
// Memory-mapped I/O is great, isn't it?
|
|
writeByte(REG_SCREEN_CTRL, 14);
|
|
// And we read it
|
|
i32 offset = readByte(REG_SCREEN_DATA);
|
|
// Since we requested the high byte
|
|
// (i.e. the 8 most significant bits)
|
|
// we now want to move them by 8 places.
|
|
// Conveniently, that's exactly what a
|
|
// left shift is for :)
|
|
offset <<= 8;
|
|
// Now we request the low byte of the cursor
|
|
// position
|
|
writeByte(REG_SCREEN_CTRL, 15);
|
|
offset += readByte(REG_SCREEN_DATA);
|
|
return offset * 2; // Times 2 because VGA cells are 2 bytes long
|
|
}
|
|
|
|
|
|
void setCursorOffset(i32 offset) {
|
|
/*
|
|
Sets the offset of the VGA text
|
|
cursor to the desired value. Note
|
|
that, this being a low level wrapper
|
|
around I/O ports, no bounds checking
|
|
is performed: the caller should make
|
|
sure that the offset fits within the
|
|
80x25 grid of the VGA screen!
|
|
*/
|
|
|
|
// We now divide by 2 because while we count
|
|
// in bytes, the VGA controller counts in cells!
|
|
offset /= 2;
|
|
// This function is almost identical to getCursorOffset,
|
|
// except we're now writing on the bus instead of reading
|
|
// from it, so the VGA controller updates the cursor position
|
|
|
|
// We write the high bits first
|
|
writeByte(REG_SCREEN_CTRL, 14);
|
|
writeByte(REG_SCREEN_DATA, (byte)(offset >> 8));
|
|
// Then the low bits
|
|
writeByte(REG_SCREEN_CTRL, 15);
|
|
writeByte(REG_SCREEN_DATA, (byte)(offset & 0xff));
|
|
// These bitwise tricks *seem* like black
|
|
// magic, but they're quite simple: shifting
|
|
// the offset by 8 bits moves the high bits 8
|
|
// positions down (so we lose the low bits and
|
|
// the high bits now fit into a single byte, which
|
|
// is what writeByte wants). Then we get rid of the
|
|
// high bits (0xff is 11110000) with a bitwise and,
|
|
// leaving us with only the low bits set which we
|
|
// then feed to the I/O port again; The reason this
|
|
// works is because 0xff (which is smaller than our
|
|
// offset), is zero-extended from the beginning with
|
|
// zeroes, so when we perform the operation the high
|
|
// bits are cancelled out
|
|
}
|
|
|
|
|
|
void clearScreen(void) {
|
|
/*
|
|
Clears the screen and resets
|
|
the cursor position to 0, 0
|
|
*/
|
|
|
|
for (i32 i = 0; i < SCREEN_SIZE; i++) {
|
|
VMEM_BUF[i * 2] = ' '; // The screen is actually never "empty", just filled with spaces!
|
|
VMEM_BUF[i * 2 + 1] = LIGHT_GREY_ON_BLACK;
|
|
}
|
|
|
|
setCursorOffset(getOffset(0, 0));
|
|
}
|
|
|
|
|
|
i32 inline getOffset(i32 col, i32 row) {
|
|
/*
|
|
Converts a column, row pair into
|
|
an absolute offset into the VGA
|
|
text buffer
|
|
*/
|
|
|
|
// We multiply by 2 because each VGA
|
|
// character cell is 2 bytes long
|
|
return (row * MAX_COLS + col) * 2;
|
|
}
|
|
|
|
|
|
i32 inline getRow(i32 offset) {
|
|
|
|
/*
|
|
Converts an absolute offset into
|
|
the VGA text buffer into a row
|
|
*/
|
|
return offset / (2 * MAX_COLS);
|
|
}
|
|
|
|
|
|
i32 inline getColumn(i32 offset) {
|
|
/*
|
|
Converts an absolute offset into
|
|
the VGA text buffer into a column
|
|
*/
|
|
return (offset - (getRow(offset) * 2 * MAX_COLS)) / 2;
|
|
}
|