From 4af94012ca71fc68b03c4f0b11248da0958315cf Mon Sep 17 00:00:00 2001 From: Nocturn9x Date: Thu, 17 Nov 2022 12:42:45 +0100 Subject: [PATCH] Implemented kprint --- src/boot/mbr.s | 22 ++-- src/boot/util/io.s | 10 -- src/kernel/drivers/ports/ports.c | 26 ++-- src/kernel/drivers/ports/ports.h | 9 +- src/kernel/drivers/vga/screen.c | 216 ++++++++++++++++++++++++++++++- src/kernel/drivers/vga/screen.h | 17 ++- src/kernel/ktypes.h | 3 +- src/kernel/main.c | 31 +---- 8 files changed, 263 insertions(+), 71 deletions(-) diff --git a/src/boot/mbr.s b/src/boot/mbr.s index 2296afe..de70614 100644 --- a/src/boot/mbr.s +++ b/src/boot/mbr.s @@ -30,15 +30,6 @@ jmp $ ; Keeps jumping at the current address (loops forever) %include "src/boot/gdt.s" %include "src/boot/switch32.s" -[bits 16] ; All x86 CPUs start in 16 bit (aka "real") mode, so we tell nasm to emit 16-bit code -load_kernel: ; Loads the kernel into memory - mov si, loading_kernel_msg - call bios_println - mov bx, kernel_offset - mov dh, 1 - mov dl, [boot_drive] - call load_disk - ret ; Here we define our variables: They need to be defined after the ; halting because otherwise they will be executed as code @@ -48,6 +39,17 @@ loading_kernel_msg: db "Loading kernel into memory", 0 boot_drive: db 0 +[bits 16] ; All x86 CPUs start in 16 bit (aka "real") mode, so we tell nasm to emit 16-bit code +load_kernel: ; Loads the kernel into memory + mov si, loading_kernel_msg + call bios_println + mov bx, kernel_offset + mov dh, 3 + mov dl, [boot_drive] + call load_disk + ret + + [bits 32] BEGIN_32BIT: ; After the switch we will get here mov esi, protected_mode_msg @@ -57,7 +59,7 @@ BEGIN_32BIT: ; After the switch we will get here ; 320 bytes so that we don't overwrite the log ; messages we have already written mov ecx, 0x140 - call vga_println + call vga_print call kernel_offset jmp $ diff --git a/src/boot/util/io.s b/src/boot/util/io.s index 8da6445..ec72f60 100644 --- a/src/boot/util/io.s +++ b/src/boot/util/io.s @@ -141,16 +141,6 @@ vga_print: ret -vga_println: - ; Prints a null-terminated string located on the - ; esi register and terminates it with a newline - pusha - call vga_print - ;mov esi, NEWLINE - ;call vga_print - popa - ret - vga_printh: ; Prints the value of edx in hexadecimal format diff --git a/src/kernel/drivers/ports/ports.c b/src/kernel/drivers/ports/ports.c index 15e8e9b..bed4101 100644 --- a/src/kernel/drivers/ports/ports.c +++ b/src/kernel/drivers/ports/ports.c @@ -3,29 +3,35 @@ #include "ports.h" -byte vga_readb(u16 port) { +// Note: We use the volatile modifier everywhere because +// the compiler might try and optimize some of our code +// away, since its effects are not directly visible from +// the code itself, which would be a nightmare to debug! + + +byte readByte(u16 port) { // Reads a byte from the specified I/O port - byte result; - __asm__("in %%dx, %%al" : "=a" (result) : "d" (port)); + volatile byte result; + __asm__ volatile ("in %%dx, %%al" : "=a" (result) : "d" (port)); return result; } -void vga_writeb(u16 port, byte data) { +void writeByte(u16 port, byte data) { // Writes a byte to the specified I/O port - __asm__("out %%al, %%dx" : : "a" (data), "d" (port)); + __asm__ volatile ("out %%al, %%dx" : : "a" (data), "d" (port)); } -u16 vga_readw(u16 port) { +u16 readWord(u16 port) { // Reads a word (16 bits) from the specified I/O port - u16 result; - __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port)); + volatile u16 result; + __asm__ volatile ("in %%dx, %%ax" : "=a" (result) : "d" (port)); return result; } -void vga_writew(u16 port, u16 data) { +void writeWord(u16 port, u16 data) { // Writes a word (16 bits) to the specified I/O port - __asm__("out %%ax, %%dx" : : "a" (data), "d" (port)); + __asm__ volatile ("out %%ax, %%dx" : : "a" (data), "d" (port)); } \ No newline at end of file diff --git a/src/kernel/drivers/ports/ports.h b/src/kernel/drivers/ports/ports.h index 5660c15..a26c64d 100644 --- a/src/kernel/drivers/ports/ports.h +++ b/src/kernel/drivers/ports/ports.h @@ -2,14 +2,13 @@ #define TSOS_DRV_PORTS_H -#define VMEM_ADDR 0xb8000 #include "kernel/ktypes.h" -byte vga_readb(u16 port); -void vga_writeb(u16 port, byte data); -u16 vga_readw(u16 port); -void vga_writew(u16 port, u16 data); +byte readByte(u16 port); +void writeByte(u16 port, byte data); +u16 readWord(u16 port); +void writeWord(u16 port, u16 data); diff --git a/src/kernel/drivers/vga/screen.c b/src/kernel/drivers/vga/screen.c index 68d466b..e3dc448 100644 --- a/src/kernel/drivers/vga/screen.c +++ b/src/kernel/drivers/vga/screen.c @@ -1,2 +1,216 @@ // Implementation of a simple text-only VGA driver -#include "screen.h" \ No newline at end of file +#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; +} diff --git a/src/kernel/drivers/vga/screen.h b/src/kernel/drivers/vga/screen.h index 20f7ea3..d38534c 100644 --- a/src/kernel/drivers/vga/screen.h +++ b/src/kernel/drivers/vga/screen.h @@ -1,23 +1,28 @@ #ifndef TSOS_DRV_VGA_SCREEN #define TSOS_DRV_VGA_SCREEN +#include "kernel/ktypes.h" +#include "kernel/drivers/ports/ports.h" + #define VMEM_ADDRESS 0xb8000 +#define VMEM_BUF ((byte*)VMEM_ADDRESS) #define MAX_ROWS 25 #define MAX_COLS 80 +#define SCREEN_SIZE MAX_ROWS * MAX_COLS #define LIGHT_GREY_ON_BLACK 0x07 #define RED_ON_WHITE 0xf4 +#define VGA_TABSIZE 4 // VGA I/O ports #define REG_SCREEN_CTRL 0x3d4 #define REG_SCREEN_DATA 0x3d5 -#include "kernel/ktypes.h" -#include "kernel/drivers/ports/ports.h" -// Exposed API -void clear_screen(void); -void kprint_at(char *message, int col, int row); -void kprint(char *message); + +// Public API +void clearScreen(void); +void kprintAt(byte* message, i32 col, i32 row); +void kprint(byte* message); #endif \ No newline at end of file diff --git a/src/kernel/ktypes.h b/src/kernel/ktypes.h index 5b0e4ca..2c3e263 100644 --- a/src/kernel/ktypes.h +++ b/src/kernel/ktypes.h @@ -8,6 +8,7 @@ typedef unsigned char byte; typedef unsigned short int u16; typedef short int i16; typedef unsigned int uint; - +typedef unsigned int u32; +typedef int i32; #endif \ No newline at end of file diff --git a/src/kernel/main.c b/src/kernel/main.c index ed9d1a0..3b5ff89 100644 --- a/src/kernel/main.c +++ b/src/kernel/main.c @@ -4,32 +4,7 @@ void kmain(void) { - byte* vga = (byte*)VMEM_ADDRESS; - vga_writeb(0x3d4, 14); // We request the high byte of cursor position on port 0x3D4 - int cursor = vga_readb(0x3d5); // And now we read it - // Since this is the high byte, we shift the - // cursor by 8 bits to make room for the low - // byte - cursor <<= 8; - // Now we request the low byte - vga_writeb(0x3d4, 15); - // And we add it to the cursor - cursor += vga_readb(0x3d5); - // VGA 'cells' are 2 bytes long, but - // the cursor counts cells as a unit, - // so we multiply it by 2 - cursor *= 2; - // We add 160 because when we print a - // message via VGA in the bootloader, - // and that leaves the cursor in the - // wrong position - cursor += 160; - // Now we write some message onto the screen - byte *s = "TSOS Kernel started"; - for (int i = 0; s[i] != '\0'; i++) { - vga[cursor] = s[i]; // We set the character code we want - cursor++; - vga[cursor] = 0x07; // Light grey on black - cursor++; - } + // Newline so we skip the log + // messages from the bootloader + kprint("\nTSOS Kernel is starting up"); } \ No newline at end of file