diff --git a/src/kernel.c b/src/kernel.c index 0ef132b..05c229c 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -1,101 +1,63 @@ -// GCC provides these header files automatically -// They give us access to useful things like fixed-width types #include #include - -// First, let's do some basic checks to make sure we are using our x86-elf cross-compiler correctly #if defined(__linux__) #error "This code must be compiled with a cross-compiler" -// #elif !defined(__i386__) -// #error "This code must be compiled with an x86-elf compiler" #endif - -// This is the x86's VGA textmode buffer. To display text, we write data to this memory location volatile uint16_t* vga_buffer = (uint16_t*)0xB8000; -// By default, the VGA textmode buffer has a size of 80x25 characters const int VGA_COLS = 80; const int VGA_ROWS = 25; - -// We start displaying text in the top-left of the screen (column = 0, row = 0) int term_col = 0; int term_row = 0; -uint8_t term_color = 0x0F; // Black background, White foreground - -// This function initiates the terminal by clearing it +uint8_t term_color = 0x0F; void term_init() { - // Clear the textmode buffer for (int col = 0; col < VGA_COLS; col ++) { for (int row = 0; row < VGA_ROWS; row ++) { - // The VGA textmode buffer has size (VGA_COLS * VGA_ROWS). - // Given this, we find an index into the buffer for our character const size_t index = (VGA_COLS * row) + col; - // Entries in the VGA buffer take the binary form BBBBFFFFCCCCCCCC, where: - // - B is the background color - // - F is the foreground color - // - C is the ASCII character - vga_buffer[index] = ((uint16_t)term_color << 8) | ' '; // Set the character to blank (a space character) + vga_buffer[index] = ((uint16_t)term_color << 8) | ' '; } } } - -// This function places a single character onto the screen void term_putc(char c) { - // Remember - we don't want to display ALL characters! switch (c) { - case '\n': // Newline characters should return the column to 0, and increment the row + case '\n': { term_col = 0; term_row ++; break; } - default: // Normal characters just get displayed and then increment the column + default: { - const size_t index = (VGA_COLS * term_row) + term_col; // Like before, calculate the buffer index + const size_t index = (VGA_COLS * term_row) + term_col; vga_buffer[index] = ((uint16_t)term_color << 8) | c; term_col ++; break; } } - - // What happens if we get past the last column? We need to reset the column to 0, and increment the row to get to a new line if (term_col >= VGA_COLS) { term_col = 0; term_row ++; } - - // What happens if we get past the last row? We need to reset both column and row to 0 in order to loop back to the top of the screen if (term_row >= VGA_ROWS) { term_col = 0; term_row = 0; } } - -// This function prints an entire string onto the screen void term_print(const char* str) { - for (size_t i = 0; str[i] != '\0'; i ++) // Keep placing characters until we hit the null-terminating character ('\0') + for (size_t i = 0; str[i] != '\0'; i ++) term_putc(str[i]); } - - - -// This is our kernel's main function void kernel_main() { - // We're here! Let's initiate the terminal and display a message to show we got here. - - // Initiate terminal term_init(); - - // Display some messages term_print("\n"); term_print(" Welcome to\n"); term_print(" _ _ ______ _ _ ______ ______________ ______________\n"); @@ -111,4 +73,4 @@ void kernel_main() term_print("| /\\ || |__| || |\\ \\ | |____ | | | | \\ / / /\n"); term_print("|_/ \\__||______||_| \\__\\|______||_____/ \\______________/ \\______________/\n"); term_print("\n Corporation Version 1.0.0\n"); -} \ No newline at end of file +} diff --git a/src/linker.ld b/src/linker.ld index 4aba17a..5b1251b 100644 --- a/src/linker.ld +++ b/src/linker.ld @@ -1,44 +1,26 @@ -/* The bootloader will start execution at the symbol designated as the entry point. In this case, that's 'start' (defined in start.s) */ ENTRY(start) - -/* Tell the linker part of the compiler where the various sections of the kernel will be put in the final kernel executable. */ SECTIONS { - /* Begin putting sections at 1 Megabyte (1M), a good place for kernels to be loaded at by the bootloader. */ - /* This is because memory below 1 Megabyte is reserved for other x86-related things, so we can't use it */ - . = 1M; - - /* We align all sections in the executable at multiples of 4 Kilobytes (4K). This will become useful later in development when we add paging */ - - /* First put the multiboot header, as it's required to be near the start of the executable otherwise the bootloader won't find it */ - /* The Multiboot header is Read-Only data, so we can put it in a '.rodata' section. */ + . = 1M; .rodata BLOCK(4K) : ALIGN(4K) { *(.multiboot) } - - /* Executable code */ .text BLOCK(4K) : ALIGN(4K) { *(.text) } - - /* Read-only data. */ .rodata BLOCK(4K) : ALIGN(4K) { *(.rodata) } - - /* Read-write data (initialized) */ .data BLOCK(4K) : ALIGN(4K) { *(.data) } - - /* Read-write data (uninitialized) and stack */ .bss BLOCK(4K) : ALIGN(4K) { *(COMMON) *(.bss) } -} \ No newline at end of file +} diff --git a/src/start.s b/src/start.s index d664b9d..248f7d8 100644 --- a/src/start.s +++ b/src/start.s @@ -1,53 +1,23 @@ -// We declare the 'kernel_main' label as being external to this file. -// That's because it's the name of the main C function in 'kernel.c'. .extern kernel_main - -// We declare the 'start' label as global (accessible from outside this file), since the linker will need to know where it is. -// In a bit, we'll actually take a look at the code that defines this label. .global start - -// Our bootloader, GRUB, needs to know some basic information about our kernel before it can boot it. -// We give GRUB this information using a standard known as 'Multiboot'. -// To define a valid 'Multiboot header' that will be recognised by GRUB, we need to hard code some -// constants into the executable. The following code calculates those constants. -.set MB_MAGIC, 0x1BADB002 // This is a 'magic' constant that GRUB will use to detect our kernel's location. -.set MB_FLAGS, (1 << 0) | (1 << 1) // This tells GRUB to 1: load modules on page boundaries and 2: provide a memory map (this is useful later in development) -// Finally, we calculate a checksum that includes all the previous values +.set MB_MAGIC, 0x1BADB002 +.set MB_FLAGS, (1 << 0) | (1 << 1) .set MB_CHECKSUM, (0 - (MB_MAGIC + MB_FLAGS)) - -// We now start the section of the executable that will contain our Multiboot header .section .multiboot .align 4 // Make sure the following data is aligned on a multiple of 4 bytes - // Use the previously calculated constants in executable code .long MB_MAGIC .long MB_FLAGS - // Use the checksum we calculated earlier .long MB_CHECKSUM - -// This section contains data initialised to zeroes when the kernel is loaded .section .bss - // Our C code will need a stack to run. Here, we allocate 4096 bytes (or 4 Kilobytes) for our stack. - // We can expand this later if we want a larger stack. For now, it will be perfectly adequate. .align 16 stack_bottom: - .skip 4096 // Reserve a 4096-byte (4K) stack + .skip 4096 stack_top: - -// This section contains our actual assembly code to be run when our kernel loads .section .text - // Here is the 'start' label we mentioned before. This is the first code that gets run in our kernel. start: - // First thing's first: we want to set up an environment that's ready to run C code. - // C is very relaxed in its requirements: All we need to do is to set up the stack. - // Please note that on x86, the stack grows DOWNWARD. This is why we start at the top. - mov $stack_top, %esp // Set the stack pointer to the top of the stack - - // Now we have a C-worthy (haha!) environment ready to run the rest of our kernel. - // At this point, we can call our main C function. + mov $stack_top, %esp call kernel_main - - // If, by some mysterious circumstances, the kernel's C code ever returns, all we want to do is to hang the CPU hang: - cli // Disable CPU interrupts - hlt // Halt the CPU - jmp hang // If that didn't work, loop around and try again. + cli + hlt + jmp hang