--- /dev/null
+ # Makefile for JamesM's kernel tutorials.
+# The C and C++ rules are already setup by default.
+# The only one that needs changing is the assembler
+# rule, as we use nasm instead of GNU as.
+
+TGT=i686
+
+SOURCES=$(TGT)/entry.o $(TGT)/utils.o module/biosvga/module.o module/console.o common/kernel_main.o
+LDSC=$(TGT)/link.ld
+
+TGTNAM=$(TGT)-elf
+CC=$(TGTNAM)-gcc
+AS=$(TGTNAM)-as
+CXX=$(TGTNAM)-g++
+LD=$(TGTNAM)-ld
+OC=$(TGTNAM)-objcopy
+NM=$(TGTNAM)-nm
+RL=$(TGTNAM)-ranlib
+
+CFLAGS=-ffreestanding -std=gnu11
+CPPFLAGS=-Iinclude -I$(TGT)/include
+LDFLAGS=-T$(LDSC)
+ASFLAGS=
+LIBS=
+
+all: link
+
+qemu: all
+ qemu-system-i386 --kernel kernel
+
+clean:
+ rm -f $(SOURCES) kernel
+
+link: $(SOURCES)
+ $(LD) $(LDFLAGS) -o kernel $(LIBS) $(SOURCES)
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(ASFLAGS) -c -o $@ $<
+
+%.o: %.cxx
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ASFLAGS) -c -o $@ $<
+
+%.o: %.S
+ $(AS) $(ASFLAGS) -c -o $@ $<
--- /dev/null
+#include <stdint.h>
+#include <module.h>
+#include <module_console.h>
+
+int kernel_main(struct multiboot *mboot_ptr)
+{
+ console_init();
+
+ kwrite("1\n");
+ kwrite("2\n");
+ kwrite("3\n");
+ kwrite("4\n");
+ kwrite("5\n");
+ kwrite("6\n");
+ kwrite("7\n");
+ kwrite("8\n");
+ kwrite("9\n");
+ kwrite("10\n");
+ kwrite("11\n");
+ kwrite("12\n");
+ kwrite("13\n");
+ kwrite("14\n");
+ kwrite("15\n");
+ kwrite("16\n");
+ kwrite("17\n");
+ kwrite("18\n");
+ kwrite("19\n");
+ kwrite("20\n");
+ kwrite("21\n");
+ kwrite("22\n");
+ kwrite("23\n");
+ kwrite("24\n");
+ kwrite("25\n");
+ kwrite("26\n");
+
+ console_deinit();
+
+ return 0xDEADBEEF;
+}
--- /dev/null
+.set ALIGN, 1<<0 # align loaded modules on page boundaries
+.set MEMINFO, 1<<1 # provide memory map
+.set FLAGS, ALIGN | MEMINFO # this is the Multiboot 'flag' field
+.set MAGIC, 0x1BADB002 # 'magic number' lets bootloader find the header
+.set CHECKSUM, -(MAGIC + FLAGS) # checksum of above, to prove we are multiboot
+
+.section .multiboot
+.align 4
+.long MAGIC
+.long FLAGS
+.long CHECKSUM
+
+.section .bss
+.align 16
+stack_bottom:
+.skip 16384 # 16 KiB
+stack_top:
+
+.section .text
+.global _start
+.type _start, @function
+_start:
+ mov $stack_top, %esp
+
+ push %ebx
+
+ call kernel_main
+
+ cli
+.halt: hlt
+ jmp .halt
+
+.size _start, . - _start
--- /dev/null
+uint8_t inb(uint16_t port);
+uint16_t inw(uint16_t port);
+uint32_t inl(uint16_t port);
+
+void outb(uint16_t port, uint8_t val);
+void outw(uint16_t port, uint16_t val);
+void outl(uint16_t port, uint32_t val);
--- /dev/null
+/* The bootloader will look at this image and start execution at the symbol
+ designated as the entry point. */
+ENTRY(_start)
+
+/* Tell where the various sections of the object files will be put in the final
+ kernel image. */
+SECTIONS
+{
+ /* Begin putting sections at 1 MiB, a conventional place for kernels to be
+ loaded at by the bootloader. */
+ . = 1M;
+
+ /* First put the multiboot header, as it is required to be put very early
+ early in the image or the bootloader won't recognize the file format.
+ Next we'll put the .text section. */
+ .text BLOCK(4K) : ALIGN(4K)
+ {
+ *(.multiboot)
+ *(.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)
+ }
+
+ /* The compiler may produce other sections, by default it will put them in
+ a segment with the same name. Simply add stuff here as needed. */
+}
--- /dev/null
+#include <stdint.h>
+
+void outb(uint16_t port, uint8_t val)
+{
+ asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) );
+}
+
+void outw(uint16_t port, uint16_t val)
+{
+ asm volatile ( "outw %0, %1" : : "a"(val), "Nd"(port) );
+}
+
+void outl(uint16_t port, uint32_t val)
+{
+ asm volatile ( "outl %0, %1" : : "a"(val), "Nd"(port) );
+}
+
+uint8_t inb(uint16_t port)
+{
+ uint8_t ret;
+ asm volatile ( "inb %1, %0"
+ : "=a"(ret)
+ : "Nd"(port) );
+ return ret;
+}
+
+uint16_t inw(uint16_t port)
+{
+ uint16_t ret;
+ asm volatile ( "inb %1, %0"
+ : "=a"(ret)
+ : "Nd"(port) );
+ return ret;
+}
+
+uint32_t inl(uint16_t port)
+{
+ uint32_t ret;
+ asm volatile ( "inb %1, %0"
+ : "=a"(ret)
+ : "Nd"(port) );
+ return ret;
+}
--- /dev/null
+typedef void (*console_put_f) (char, char, char, uint16_t, uint16_t);
+typedef void (*console_get_f) (char*, char*, char*, uint16_t, uint16_t);
+typedef void (*console_cursor_f) (uint16_t, uint16_t);
+typedef uint16_t (*console_width_f) ();
+typedef uint16_t (*console_height_f) ();
+typedef void (*console_refresh_f)();
+
+struct console_module {
+ console_put_f put;
+ console_get_f get;
+ console_cursor_f cursor;
+ console_width_f width;
+ console_height_f height;
+ console_refresh_f refresh;
+};
+
+void console_register_module(struct console_module*);
+void console_unregister_module(struct console_module*);
+void console_init();
+void console_deinit();
+
+void kputc(char);
+void kwrite(char*);
+
--- /dev/null
+#include <stdint.h>
+#include <module.h>
+#include <module_console.h>
+
+uint16_t *video_mem = (uint16_t*)0xB8000;
+
+void biosvga_put(char c, char color_fg, char color_bg, uint16_t x, uint16_t y) {
+ uint8_t attr = (color_bg << 4) | (color_fg & 0xF);
+ uint16_t write = (attr << 8) | c;
+
+ video_mem[y * 80 + x] = write;
+}
+
+void biosvga_get(char* c, char* color_fg, char* color_bg, uint16_t x, uint16_t y) {
+ uint16_t write = video_mem[y * 80 + x];
+ *c = (char)(write & 0xFF);
+ *color_bg = (write << 12) & 0xF;
+ *color_fg = (write << 8) & 0xF;
+}
+
+void biosvga_cursor(uint16_t x, uint16_t y) {
+ uint16_t loc = y * 80 + x;
+ outb(0x3D4, 14);
+ outb(0x3D5, loc >> 8);
+ outb(0x3D4, 15);
+ outb(0x3D5, loc);
+}
+
+uint16_t biosvga_width() {
+ return 80;
+}
+
+uint16_t biosvga_height() {
+ return 25;
+}
+
+void biosvga_refresh() {
+
+}
+
+struct console_module biosvga_module = {
+ .put = biosvga_put,
+ .get = biosvga_get,
+ .cursor = biosvga_cursor,
+ .width = biosvga_width,
+ .height = biosvga_height,
+ .refresh = biosvga_refresh
+};
+
+void biosvga_init() {
+ console_register_module(&biosvga_module);
+}
+
+void biosvga_deinit() {
+ console_unregister_module(&biosvga_module);
+ // Stub.
+}
--- /dev/null
+/* This is a generic-ish interface for console drivers. The main reason for this is to abstract printing from what it is
+actually printing to; in the future, adding a new backend for framebuffer rendering will be easy, for instance. */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <module.h>
+#include <module_console.h>
+
+struct console_module *active;
+uint16_t x, y, tab_w;
+
+void biosvga_init();
+void biosvga_deinit();
+
+void console_register_module(struct console_module* module) {
+ active = module;
+}
+
+void console_unregister_module(struct console_module* module) {
+ if (active == module)
+ active = NULL;
+}
+
+// TODO - Should this be part of the backend? Scrolling is slow.
+void scroll() {
+ if (y >= active->height()) {
+ for(int cy = 1; cy <= active->height(); cy++) {
+ for(int cx = 0; cx <= active->width(); cx++) {
+ char c = 0;
+ uint8_t fg = 0, bg = 0;
+ active->get(&c, &fg, &bg, cx, cy);
+ active->put(c, 0xf, 0, cx, cy-1);
+ }
+ }
+ --y;
+ for(int cx = 0; cx < active->width(); cx++) {
+ active->put(' ', 0xf, 0, cx, y);
+ }
+ }
+}
+
+// TODO - Should this be part of the backend? Scrolling is slow.
+void clear() {
+ x = y = 0;
+ for(int cy = 0; cy < active->height(); cy++) {
+ for(int cx = 0; cx < active->width(); cx++) {
+ active->put(' ', 0xf, 0x0, cx, cy);
+ }
+ }
+}
+
+void kputc(char c) {
+ switch(c) {
+ case '\t':
+ x += tab_w - (x % tab_w);
+ break;
+ case '\b':
+ --x;
+ break;
+ case '\n':
+ ++y;
+ case '\r':
+ x = 0;
+ break;
+ case ' ' ... '~':
+ active->put(c, 0xf, 0x0, x, y);
+ default:
+ x += 1;
+ break;
+ }
+
+ if (x > active->width()) {
+ x = 0;
+ ++y;
+ }
+
+ scroll();
+ active->cursor(x, y);
+}
+
+void kwrite(char* str) {
+ while(*str != 0) {
+ kputc(*str);
+ ++str;
+ }
+}
+
+void console_init() {
+ x = y = 0;
+ tab_w = 4;
+
+ // Temporary
+ biosvga_init();
+
+ clear();
+}
+
+void console_deinit() {
+ // Temporary
+ biosvga_deinit();
+}
+