|
|
@ -19,67 +19,81 @@ limitations under the License. |
|
|
|
#include "kernel/types.h" |
|
|
|
#include "kernel/util.h" |
|
|
|
|
|
|
|
#include <stdarg.h> |
|
|
|
|
|
|
|
|
|
|
|
u16 getCursorOffset(void); |
|
|
|
void setCursorOffset(u16 offset); |
|
|
|
u16 putchar(char ch, u8 row, u8 col, char attr); |
|
|
|
u16 getOffset(u8 row, u8 col); |
|
|
|
u8 getRow(u16 offset); |
|
|
|
u8 getColumn(u16 offset); |
|
|
|
void printc(char c); |
|
|
|
|
|
|
|
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(char* message, i32 col, i32 row) { |
|
|
|
void kprint(const char* message) { |
|
|
|
/*
|
|
|
|
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 |
|
|
|
text buffer |
|
|
|
*/ |
|
|
|
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); |
|
|
|
i32 offset = getCursorOffset(); |
|
|
|
i32 row = getRow(offset), col = getColumn(offset); |
|
|
|
while (*message) { |
|
|
|
offset = putchar(*(message++), row, col, 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); |
|
|
|
} |
|
|
|
|
|
|
|
setCursorOffset(getOffset(row, col)); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void inline kprint(char* message) { |
|
|
|
|
|
|
|
void kprintf(const char *fmt, ...) { |
|
|
|
/*
|
|
|
|
Prints a null-terminated string to the |
|
|
|
VGA text buffer |
|
|
|
A simple reimplementation of printf in |
|
|
|
kernel space. The following basic format |
|
|
|
specifiers can be used: |
|
|
|
- %s -> string |
|
|
|
- %d -> signed number |
|
|
|
- %u -> unsigned number |
|
|
|
- %c -> character |
|
|
|
*/ |
|
|
|
kprintAt(message, -1, -1); |
|
|
|
va_list args; |
|
|
|
va_start(args, fmt); |
|
|
|
char* sbuf; /* Used to print strings */ |
|
|
|
char ibuf[12]; /* Used as input to itoa. 12 is the number of
|
|
|
|
digits of 2 ** 32 + 2 (for the sign and null byte)*/ |
|
|
|
u32 offset = getCursorOffset(); |
|
|
|
u32 row = getRow(offset), col = getColumn(offset); |
|
|
|
memset(ibuf, '0', 12); |
|
|
|
while (*fmt) { |
|
|
|
offset = putchar(*(fmt++), row, col, 0); |
|
|
|
row = getRow(offset); |
|
|
|
col = getColumn(offset); |
|
|
|
} |
|
|
|
va_end(args); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void inline kprintln(char* message) { |
|
|
|
void inline kprintln(const char* message) { |
|
|
|
/*
|
|
|
|
Identical to kprint, but calls |
|
|
|
kprint("\n") afterwards |
|
|
|
*/ |
|
|
|
kprint(message); |
|
|
|
kprint("\n"); |
|
|
|
kprint(message); |
|
|
|
kprint("\n"); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Private API below
|
|
|
|
|
|
|
|
i32 putchar(byte ch, i32 col, i32 row, byte attr) { |
|
|
|
u16 putchar(char ch, u8 row, u8 col, char attr) { |
|
|
|
/*
|
|
|
|
Writes a single character to the VGA text buffer |
|
|
|
at the given row and column and returns the new |
|
|
@ -108,16 +122,16 @@ i32 putchar(byte ch, i32 col, i32 row, byte attr) { |
|
|
|
if (row >= MAX_ROWS) { |
|
|
|
return -2; |
|
|
|
} |
|
|
|
i32 offset = (col >= 0 && row >= 0)? getOffset(col, row): getCursorOffset(); |
|
|
|
u16 offset = (col >= 0 && row >= 0)? getOffset(row, col): 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)); |
|
|
|
offset = getOffset(getRow(offset), 0); |
|
|
|
break; |
|
|
|
case '\n': |
|
|
|
offset = getOffset(0, getRow(offset) + 1); |
|
|
|
offset = getOffset(getRow(offset) + 1, 0); |
|
|
|
break; |
|
|
|
case '\t': |
|
|
|
for (i32 i = 0; i < VGA_TABSIZE; i++) { |
|
|
@ -135,29 +149,28 @@ i32 putchar(byte ch, i32 col, i32 row, byte attr) { |
|
|
|
// We check if we reached the end of the screen, in which
|
|
|
|
// case we scroll
|
|
|
|
if (offset >= SCREEN_SIZE * 2) { |
|
|
|
for (i32 i = 1; i < MAX_ROWS; i ++) { |
|
|
|
for (u8 i = 1; i < MAX_ROWS; i++) { |
|
|
|
// This loop will take the bytes of row i and copy
|
|
|
|
// them to row i - 1, effectively erasing the first
|
|
|
|
// one and causing the text on the screen to scroll!
|
|
|
|
copystr((char *)(getOffset(0, i) + VMEM_ADDRESS), |
|
|
|
(char*)(getOffset(0, i - 1) + VMEM_ADDRESS), |
|
|
|
copystr((char*)(getOffset(i, 0) + VMEM_ADDRESS), |
|
|
|
(char*)(getOffset(i - 1, 0) + VMEM_ADDRESS), |
|
|
|
MAX_COLS * 2); |
|
|
|
} |
|
|
|
// We empty the last line
|
|
|
|
char* last = (char*)(getOffset(0, MAX_ROWS - 1) + VMEM_ADDRESS); |
|
|
|
for (i32 i = 0; i < MAX_COLS * 2; i++) { |
|
|
|
char* last = (char*)(getOffset(MAX_ROWS - 1, 0) + VMEM_ADDRESS); |
|
|
|
for (u8 i = 0; i < MAX_COLS * 2; i++) { |
|
|
|
last[i] = 0; |
|
|
|
} |
|
|
|
|
|
|
|
offset -= 2 * MAX_COLS; |
|
|
|
} |
|
|
|
|
|
|
|
setCursorOffset(offset); |
|
|
|
return offset; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
i32 getCursorOffset(void) { |
|
|
|
u16 getCursorOffset(void) { |
|
|
|
/*
|
|
|
|
Returns the current offset of |
|
|
|
the VGA text cursor |
|
|
@ -170,7 +183,7 @@ i32 getCursorOffset(void) { |
|
|
|
// Memory-mapped I/O is great, isn't it?
|
|
|
|
writeByte(REG_SCREEN_CTRL, 14); |
|
|
|
// And we read it
|
|
|
|
i32 offset = readByte(REG_SCREEN_DATA); |
|
|
|
u16 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.
|
|
|
@ -185,7 +198,7 @@ i32 getCursorOffset(void) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void setCursorOffset(i32 offset) { |
|
|
|
void setCursorOffset(u16 offset) { |
|
|
|
/*
|
|
|
|
Sets the offset of the VGA text |
|
|
|
cursor to the desired value. Note |
|
|
@ -193,11 +206,11 @@ void setCursorOffset(i32 offset) { |
|
|
|
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! |
|
|
|
80x25 grid of the VGA screen |
|
|
|
*/ |
|
|
|
|
|
|
|
// We now divide by 2 because while we count
|
|
|
|
// in bytes, the VGA controller counts in cells!
|
|
|
|
// 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
|
|
|
@ -205,23 +218,11 @@ void setCursorOffset(i32 offset) { |
|
|
|
|
|
|
|
// We write the high bits first
|
|
|
|
writeByte(REG_SCREEN_CTRL, 14); |
|
|
|
writeByte(REG_SCREEN_DATA, (byte)(offset >> 8)); |
|
|
|
writeByte(REG_SCREEN_DATA, HIGH8(offset)); |
|
|
|
// 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
|
|
|
|
writeByte(REG_SCREEN_DATA, LOW8(offset)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -230,17 +231,16 @@ 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] = ' '; // 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) { |
|
|
|
u16 inline getOffset(u8 row, u8 col) { |
|
|
|
/*
|
|
|
|
Converts a column, row pair into |
|
|
|
an absolute offset into the VGA |
|
|
@ -253,17 +253,21 @@ i32 inline getOffset(i32 col, i32 row) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
i32 inline getRow(i32 offset) { |
|
|
|
u8 inline getRow(u16 offset) { |
|
|
|
|
|
|
|
/*
|
|
|
|
Converts an absolute offset into |
|
|
|
the VGA text buffer into a row |
|
|
|
*/ |
|
|
|
|
|
|
|
// Conversely, we divide by two
|
|
|
|
// when converting back from the
|
|
|
|
// raw offset to columns/rows
|
|
|
|
return offset / (2 * MAX_COLS); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
i32 inline getColumn(i32 offset) { |
|
|
|
u8 inline getColumn(u16 offset) { |
|
|
|
/*
|
|
|
|
Converts an absolute offset into |
|
|
|
the VGA text buffer into a column |
|
|
|