diff --git a/Makefile b/Makefile index 39d2038..0ee093b 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ DEPS := $(OBJS:.o=.d) INC_DIRS := $(shell find $(SRC_DIRS) -type d) INC_FLAGS := $(addprefix -I,$(INC_DIRS)) -CPPFLAGS ?= $(INC_FLAGS) -MMD -MP -g -std=c++11 -Wall +CPPFLAGS ?= $(INC_FLAGS) -MMD -MP -g -std=c++17 -Wall # Default makefile target all: os-image diff --git a/src/cpu/types.h b/src/cpu/types.h new file mode 100644 index 0000000..1616746 --- /dev/null +++ b/src/cpu/types.h @@ -0,0 +1,15 @@ +#ifndef KERNEL_CPU_TYPES_H +#define KERNEL_CPU_TYPES_H + +// Non-semantic types specific to 32-bit Intel x86 +typedef unsigned int u32_t; +typedef int i32_t; +typedef unsigned short u16_t; +typedef short i16_t; +typedef unsigned char u8_t; +typedef char i8_t; + +#define LOW16(address) (u16)((address) & 0xffff) +#define HIGH16(address) (u16)(((address) >> 16) & 0xffff) + +#endif //KERNEL_CPU_TYPES_H diff --git a/src/drivers/.gitkeep b/src/drivers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/drivers/vga.cpp b/src/drivers/vga.cpp new file mode 100644 index 0000000..a672a52 --- /dev/null +++ b/src/drivers/vga.cpp @@ -0,0 +1,149 @@ +#include "../kernel/io.h" +#include "vga.h" + +int VGA::getOffset(int col, int row) { + return 2 * (row * VGA_MAX_COLS + col); +} + +int VGA::getOffsetRow(int offset) { + return offset / (2 * VGA_MAX_COLS); +} + +int VGA::getOffsetCol(int offset) { + return (offset - (getOffsetRow(offset) * 2 * VGA_MAX_COLS)) / 2; +} + +int VGA::getCursorOffset() { + io_byte_out(REG_VGA_CTL, 14); + int offset = io_byte_in(REG_VGA_DATA) << 8; // High byte + + io_byte_out(REG_VGA_CTL, 15); + offset += io_byte_in(REG_VGA_DATA); // Low byte + + return offset * 2; // Character cell is 2 bytes, so * position by 2 +} + +void VGA::setCursorOffset(int offset) { + offset /= 2; // Char cell is 2 bytes, so divide the memory offset by 2 to get position + io_byte_out(REG_VGA_CTL, 14); // write the high-byte + io_byte_out(REG_VGA_DATA, (unsigned char)(offset >> 8)); // get only the high-byte + io_byte_out(REG_VGA_CTL, 15); // write the low-byte + io_byte_out(REG_VGA_DATA, (unsigned char)(offset & 0xff)); // get only the low-byte +} + +int VGA::printChar(char character, int col, int row, char attrs) { + // Create a char pointer to the start of video memory + unsigned char* vidmem = (unsigned char*) VGA_ADDRESS; + + // If the attributes are nullish, then assume the default + if ( !attrs ) { + attrs = VGA_WHITE_ON_BLACK; + } + + // Calculate the video memory offset for the current screen location + int offset; + + // If col & row are non-negative, use those + if ( col >= 0 && row >= 0 ) { + offset = getOffset(col, row); + } else { + // Otherwise, use the current location of the cursor. + offset = getCursorOffset(); + } + + // If we see a newline character, advance offset to the end of the current row, + // so it will be advanced to the first column of the next row. + if ( character == '\n' ) { + int current_row = getOffsetRow(offset); + offset = getOffset(VGA_MAX_COLS - 1, current_row); + } else { + // Otherwise, write the char and its attr byte to the specified vidmem offset + vidmem[offset] = character; + vidmem[offset + 1] = attrs; + } + + // Handle scrolling, when we reach the bottom of the screen + offset = adjustScrolling(offset); + + // Update the offset to the next cell, 2 bytes ahead of the current one + offset += 2; + + // Update the cursor position on the screen device + setCursorOffset(offset); + + return offset; +} + +void VGA::clearScreen() { + int screen_size = VGA_MAX_COLS * VGA_MAX_ROWS; + unsigned char* vidmem = (unsigned char*) VGA_ADDRESS; + + for ( int i = 0; i < screen_size; i += 1 ) { + vidmem[i * 2] = ' '; + vidmem[(i * 2) + 1] = VGA_WHITE_ON_BLACK; + } + + setCursorOffset(getOffset(0, 0)); +} + +int VGA::adjustScrolling(int offset) { + int row = getOffsetRow(offset); + int col = getOffsetCol(offset); + + if ( row >= (VGA_MAX_ROWS - 1) && col >= (VGA_MAX_COLS - 2) ) { + advanceScrolling(); + return getOffset(col, row - 1); + } + + return offset; +} + +void VGA::advanceScrolling() { + int screen_size = VGA_MAX_COLS * VGA_MAX_ROWS; + unsigned char* vidmem = (unsigned char*) VGA_ADDRESS; + + // Loop over every character cell on the screen + for ( int i = 0; i < screen_size; i += 1 ) { + // Compute the existing offset & row/column + int offset = i * 2; + int col = getOffsetCol(offset); + int row = getOffsetRow(offset); + + if ( row > 0 ) { + // If this is not the top (scrolled-off) row, copy the char + // to the same column in the row above it. + int new_offset = getOffset(col, row - 1); + vidmem[new_offset] = vidmem[offset]; + vidmem[new_offset + 1] = vidmem[offset + 1]; + } + + if ( row == (VGA_MAX_ROWS - 1) ) { + // If this is the bottom row, clear the cells. + vidmem[offset] = ' '; + vidmem[offset + 1] = VGA_WHITE_ON_BLACK; + } + } +} + +void VGA::printAt(char* string, int col, int row) { + // Update the cursor if col & row nonnegative + if ( col >= 0 && row >= 0 ) { + setCursorOffset(getOffset(col, row)); + } + + // Loop through each char of the string and print it + int i = 0; + int offset; + while ( string[i] != 0 ) { + offset = printChar(string[i], col, row, VGA_WHITE_ON_BLACK); + + // Compute row/col for next iteration + i += 1; + col = getOffsetCol(offset); + row = getOffsetRow(offset); + } +} + +void VGA::print(char* string) { + printAt(string, -1, -1); +} diff --git a/src/drivers/vga.h b/src/drivers/vga.h new file mode 100644 index 0000000..5ab2cbc --- /dev/null +++ b/src/drivers/vga.h @@ -0,0 +1,83 @@ +#ifndef KERNEL_VGA_H +#define KERNEL_VGA_H + +// Base memory address for MM VGA +#define VGA_ADDRESS 0xb8000 + +// Max character cell dimensions +#define VGA_MAX_ROWS 25 +#define VGA_MAX_COLS 80 + +// Default color scheme +#define VGA_WHITE_ON_BLACK 0x0f + +// Screen device I/O ports +#define REG_VGA_CTL 0x3d4 +#define REG_VGA_DATA 0x3d5 + +class VGA { +protected: + /** + * Given a column and row of a VGA character cell, compute the memory + * offset from the beginning of the VGA video memory of its char and + * attr bytes. + * @param col + * @param row + * @return the computed offset + */ + int getOffset(int col, int row); + + int getOffsetRow(int offset); + + int getOffsetCol(int offset); + + /** + * Get the memory offset of the character cell where the cursor is. + * + * 1. Ask for the high-byte of the offset (data 14) + * 2. Ask for the low-byte (data 15) + * + * @return + */ + int getCursorOffset(); + + /** + * Given a VGA memory offset, set the cursor to that offset's character cell. + * @param offset + */ + void setCursorOffset(int offset); + + /** + * Print a character to the specified position. + * + * If col/row are negative, print to the current cursor position. + * If attribute_byte is 0, default white on black. + * + * @param character + * @param col + * @param row + * @param attribute_byte + */ + int printChar(char character, int col, int row, char attrs); + + int adjustScrolling(int offset); + + /** + * Scrolls all the character cells up one row, replacing the top + * row and creating a new empty row on the bottom. + */ + void advanceScrolling(); + +public: + /** + * Resets all video memory characters to empty space and all formatting + * to the default white on black. Then, reset the cursor to the first cell. + */ + void clearScreen(); + + void printAt(char* string, int col, int row); + + void print(char* string); +}; + +#endif //KERNEL_VGA_H diff --git a/src/kernel.cpp b/src/kernel.cpp deleted file mode 100644 index 5c111e0..0000000 --- a/src/kernel.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// Called by boot.asm -void main() { - // Create pointer to a char and point to the first text cell of VGA - char* video_memory = (char*) 0xb8000; - - // Write the char X to the video memory to display it on the top-left of screen - *video_memory = 'X'; -} diff --git a/src/kernel/io.cpp b/src/kernel/io.cpp new file mode 100644 index 0000000..428e6be --- /dev/null +++ b/src/kernel/io.cpp @@ -0,0 +1,57 @@ +#include "util.h" + +/** + * Wrapper function that reads a byte from the specified I/O port. + * + * "=a" (result) - put al register in the `result` var when finished + * "d" (port) - load edx with the specified `port` + * + * @param port + * @return the read byte + */ +u8_t io_byte_in(u16_t port) { + u8_t result; + __asm__("in %%dx, %%al" : "=a" (result) : "d" (port)); + return result; +} + +/** + * Wrapper function that writes a byte to the specified I/O port. + * + * "a" (data) - load eal with `data` + * "d" (port) - load edx with `port` + * + * @param port + * @param data + */ +void io_byte_out(u16_t port, u8_t data) { + __asm__("out %%al, %%dx" : : "a" (data), "d" (port)); +} + +/** + * Wrapper function that reads a word from the specified I/O port. + * + * "=a" (result) - put eax register in the `result` var when finished + * "d" (port) - load edx with the specified `port` + * + * @param port + * @return the read word + */ +u16_t io_word_in(u16_t port) { + u16_t result; + __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port)); + return result; +} + +/** + * Wrapper function that writes a word to the specified I/O port. + * + * "a" (data) - load eax with `data` + * "d" (port) - load edx with `port` + * + * @param port + * @param data + */ +void io_word_out(u16_t port, u16_t data) { + __asm__("out %%ax, %%dx" : : "a" (data), "d" (port)); +} diff --git a/src/kernel/io.h b/src/kernel/io.h new file mode 100644 index 0000000..45ba895 --- /dev/null +++ b/src/kernel/io.h @@ -0,0 +1,48 @@ +#ifndef KERNEL_IO_H +#define KERNEL_IO_H + +/** + * Wrapper function that reads a byte from the specified I/O port. + * + * "=a" (result) - put al register in the `result` var when finished + * "d" (port) - load edx with the specified `port` + * + * @param port + * @return the read byte + */ +u8_t io_byte_in(u16_t port); + +/** + * Wrapper function that writes a byte to the specified I/O port. + * + * "a" (data) - load eal with `data` + * "d" (port) - load edx with `port` + * + * @param port + * @param data + */ +void io_byte_out(u16_t port, u8_t data); + +/** + * Wrapper function that reads a word from the specified I/O port. + * + * "=a" (result) - put eax register in the `result` var when finished + * "d" (port) - load edx with the specified `port` + * + * @param port + * @return the read word + */ +u16_t io_word_in(u16_t port); + +/** + * Wrapper function that writes a word to the specified I/O port. + * + * "a" (data) - load eax with `data` + * "d" (port) - load edx with `port` + * + * @param port + * @param data + */ +void io_word_out(u16_t port, u16_t data); + +#endif //KERNEL_IO_H diff --git a/src/kernel/main.cpp b/src/kernel/main.cpp new file mode 100644 index 0000000..4a1de0e --- /dev/null +++ b/src/kernel/main.cpp @@ -0,0 +1,9 @@ +#include "../drivers/vga.h" + +// Called by boot.asm +void main() { + VGA vga; + + vga.clearScreen(); + vga.print("Kernel booted."); +} diff --git a/src/kernel/mem.cpp b/src/kernel/mem.cpp new file mode 100644 index 0000000..90897b2 --- /dev/null +++ b/src/kernel/mem.cpp @@ -0,0 +1,14 @@ + +/** + * Copy `num_bytes` many bytes from the `source` memory to the + * `destination` memory. + * + * @param source + * @param destination + * @param num_bytes + */ +void mem_copy(char* source, char* destination, int num_bytes) { + for ( int i = 0; i < num_bytes; i += 1 ) { + *(destination + i) = *(source + i); + } +} diff --git a/src/kernel/mem.h b/src/kernel/mem.h new file mode 100644 index 0000000..41526a0 --- /dev/null +++ b/src/kernel/mem.h @@ -0,0 +1,6 @@ +#ifndef KERNEL_MEM_H +#define KERNEL_MEM_H + +void mem_copy(char* source, char* destination, int num_bytes); + +#endif //KERNEL_MEM_H diff --git a/src/kernel/util.h b/src/kernel/util.h new file mode 100644 index 0000000..7569fa2 --- /dev/null +++ b/src/kernel/util.h @@ -0,0 +1,6 @@ +#ifndef KERNEL_UTIL_H +#define KERNEL_UTIL_H + +#include "../cpu/types.h" + +#endif //KERNEL_UTIL_H