summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthe lemons <citrons@mondecitronne.com>2021-03-31 00:35:03 -0500
committerthe lemons <citrons@mondecitronne.com>2021-03-31 00:35:03 -0500
commite03bf1f1ec1230117ca358a75624f2c50c01f045 (patch)
tree113c0d0360abec753998ced5fc59f7552e603524
initial files
-rw-r--r--Makefile54
-rw-r--r--asm_obj/boot.obin0 -> 18332 bytes
-rw-r--r--asm_obj/interrupt_stub.obin0 -> 1620 bytes
-rw-r--r--grub.cfg6
-rw-r--r--isodir/boot/grub/grub.cfg6
-rwxr-xr-xisodir/boot/kernel.binbin0 -> 86632 bytes
-rwxr-xr-xkernel.binbin0 -> 86632 bytes
-rw-r--r--linker.ld26
-rw-r--r--obj/bees.obin0 -> 4076 bytes
-rw-r--r--obj/interrupt.obin0 -> 8668 bytes
-rw-r--r--obj/keyboard.obin0 -> 6180 bytes
-rw-r--r--obj/main.obin0 -> 6720 bytes
-rw-r--r--obj/port.obin0 -> 3168 bytes
-rw-r--r--obj/printf.obin0 -> 32208 bytes
-rw-r--r--obj/string.obin0 -> 5652 bytes
-rw-r--r--obj/vga.obin0 -> 7656 bytes
-rw-r--r--os.isobin0 -> 19890176 bytes
-rw-r--r--src/.tags119
-rw-r--r--src/bees.c22
-rw-r--r--src/bees.h6
-rw-r--r--src/boot.s80
-rw-r--r--src/interrupt.c92
-rw-r--r--src/interrupt.h46
-rw-r--r--src/interrupt_stub.s54
-rw-r--r--src/keyboard.c98
-rw-r--r--src/keyboard.h15
-rw-r--r--src/main.c45
-rw-r--r--src/port.c38
-rw-r--r--src/port.h19
-rw-r--r--src/printf.c914
-rw-r--r--src/printf.h117
-rw-r--r--src/string.c64
-rw-r--r--src/string.h15
-rw-r--r--src/vga.c103
-rw-r--r--src/vga.h50
35 files changed, 1989 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3d52bf6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,54 @@
+NAME = os
+
+CC = i686-elf-gcc
+ASM = i686-elf-as
+CFLAGS = -std=gnu99 -ffreestanding -O2 -Wall -Wextra -g
+LFLAGS = -ffreestanding -O2 -nostdlib -lgcc -g
+
+SDIR = src
+ODIR = obj
+SRC = $(wildcard $(SDIR)/*.c)
+OBJ = $(patsubst $(SDIR)/%.c,$(ODIR)/%.o,$(SRC))
+
+ASMODIR = asm_obj
+ASMSRC = $(wildcard $(SDIR)/*.s)
+ASMOBJ = $(patsubst $(SDIR)/%.s,$(ASMODIR)/%.o,$(ASMSRC))
+
+default: kernel.bin
+
+kernel.bin: $(OBJ) $(ASMOBJ)
+ $(CC) -T linker.ld -o $@ $^ $(LFLAGS)
+
+$(ODIR)/%.o: $(SDIR)/%.c $(ODIR)
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+$(ASMODIR)/%.o: $(SDIR)/%.s $(ASMODIR)
+ $(ASM) -g -c -o $@ $<
+
+$(NAME).iso: kernel.bin
+ mkdir -p isodir/boot/grub
+ cp $< isodir/boot/
+ cp grub.cfg isodir/boot/grub/grub.cfg
+ grub-mkrescue -o $@ isodir
+
+.PHONY: test debug
+test: os.iso
+ qemu-system-i386 -cdrom os.iso
+
+debug: kernel.bin
+ qemu-system-i386 -s -S -kernel kernel.bin
+
+$(ODIR):
+ mkdir -p $@
+
+$(ASMODIR):
+ mkdir -p $@
+
+.PHONY: clean
+
+clean:
+ rm -rf $(ODIR)
+ rm -rf $(ASMODIR)
+ rm -f boot.o
+ rm -rf isodir
+ rm -f $(NAME).iso
diff --git a/asm_obj/boot.o b/asm_obj/boot.o
new file mode 100644
index 0000000..884c973
--- /dev/null
+++ b/asm_obj/boot.o
Binary files differ
diff --git a/asm_obj/interrupt_stub.o b/asm_obj/interrupt_stub.o
new file mode 100644
index 0000000..669b218
--- /dev/null
+++ b/asm_obj/interrupt_stub.o
Binary files differ
diff --git a/grub.cfg b/grub.cfg
new file mode 100644
index 0000000..84f9dfd
--- /dev/null
+++ b/grub.cfg
@@ -0,0 +1,6 @@
+set timeout=0
+set default=0
+
+menuentry "citrons os (good)" {
+ multiboot /boot/kernel.bin
+}
diff --git a/isodir/boot/grub/grub.cfg b/isodir/boot/grub/grub.cfg
new file mode 100644
index 0000000..84f9dfd
--- /dev/null
+++ b/isodir/boot/grub/grub.cfg
@@ -0,0 +1,6 @@
+set timeout=0
+set default=0
+
+menuentry "citrons os (good)" {
+ multiboot /boot/kernel.bin
+}
diff --git a/isodir/boot/kernel.bin b/isodir/boot/kernel.bin
new file mode 100755
index 0000000..c0362ec
--- /dev/null
+++ b/isodir/boot/kernel.bin
Binary files differ
diff --git a/kernel.bin b/kernel.bin
new file mode 100755
index 0000000..c0362ec
--- /dev/null
+++ b/kernel.bin
Binary files differ
diff --git a/linker.ld b/linker.ld
new file mode 100644
index 0000000..cd22338
--- /dev/null
+++ b/linker.ld
@@ -0,0 +1,26 @@
+
+ENTRY(_start)
+
+SECTIONS
+{
+ /* Begin at 1 MiB */
+ . = 1M;
+
+ /* Multiboot header */
+ .text BLOCK(4K) : ALIGN(4K)
+ {
+ *(.multiboot)
+ *(.text)
+ }
+
+ .rodata BLOCK(4K) : ALIGN(4K)
+ {
+ *(.data)
+ }
+
+ .bss BLOCK(4K) : ALIGN(4K)
+ {
+ *(COMMON)
+ *(.bss)
+ }
+}
diff --git a/obj/bees.o b/obj/bees.o
new file mode 100644
index 0000000..5014992
--- /dev/null
+++ b/obj/bees.o
Binary files differ
diff --git a/obj/interrupt.o b/obj/interrupt.o
new file mode 100644
index 0000000..62fa47b
--- /dev/null
+++ b/obj/interrupt.o
Binary files differ
diff --git a/obj/keyboard.o b/obj/keyboard.o
new file mode 100644
index 0000000..f8457ab
--- /dev/null
+++ b/obj/keyboard.o
Binary files differ
diff --git a/obj/main.o b/obj/main.o
new file mode 100644
index 0000000..4e314ac
--- /dev/null
+++ b/obj/main.o
Binary files differ
diff --git a/obj/port.o b/obj/port.o
new file mode 100644
index 0000000..3643494
--- /dev/null
+++ b/obj/port.o
Binary files differ
diff --git a/obj/printf.o b/obj/printf.o
new file mode 100644
index 0000000..fea2cec
--- /dev/null
+++ b/obj/printf.o
Binary files differ
diff --git a/obj/string.o b/obj/string.o
new file mode 100644
index 0000000..4afe28a
--- /dev/null
+++ b/obj/string.o
Binary files differ
diff --git a/obj/vga.o b/obj/vga.o
new file mode 100644
index 0000000..44460d2
--- /dev/null
+++ b/obj/vga.o
Binary files differ
diff --git a/os.iso b/os.iso
new file mode 100644
index 0000000..db860d2
--- /dev/null
+++ b/os.iso
Binary files differ
diff --git a/src/.tags b/src/.tags
new file mode 100644
index 0000000..d249904
--- /dev/null
+++ b/src/.tags
@@ -0,0 +1,119 @@
+!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
+!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
+!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
+!_TAG_PROGRAM_NAME Exuberant Ctags //
+!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
+!_TAG_PROGRAM_VERSION 5.9~svn20110310 //
+GDT_H gdt.h 3;" d
+INTERRUPT_H interrupt.h 3;" d
+KERNEL main.c 2;" d file:
+PORT_H port.h 3;" d
+STA_R gdt.h 10;" d
+STA_W gdt.h 9;" d
+STA_X gdt.h 8;" d
+STRING_H string.h 3;" d
+STS_IG32 gdt.h 13;" d
+STS_T32A gdt.h 12;" d
+STS_TG32 gdt.h 14;" d
+VGA_H vga.h 3;" d
+VGA_HEIGHT vga.h /^static const size_t VGA_HEIGHT = 25;$/;" v
+VGA_WIDTH vga.h /^static const size_t VGA_WIDTH = 80;$/;" v
+_start boot.s /^_start:$/;" l
+access interrupt.h /^ uint8_t access; \/\/ Access rights$/;" m struct:gate_desc
+bandwidth port.h /^ port16bit, port32bit} bandwidth;$/;" m struct:port typeref:enum:port::__anon1
+base interrupt.h /^ uint32_t base;$/;" m struct:idt_pointer
+base_15_0 gdt.h /^ unsigned int base_15_0 : 16; \/\/ Low bits of segment base address$/;" m struct:gdt_seg
+base_23_16 gdt.h /^ unsigned int base_23_16 : 8; \/\/ Middle bits of segment base address$/;" m struct:gdt_seg
+base_31_24 gdt.h /^ unsigned int base_31_24 : 8; \/\/ High bits of segment base address$/;" m struct:gdt_seg
+code gdt.h /^ struct gdt_seg code;$/;" m struct:kernel_gdt typeref:struct:kernel_gdt::gdt_seg
+data gdt.h /^ struct gdt_seg data;$/;" m struct:kernel_gdt typeref:struct:kernel_gdt::gdt_seg
+db gdt.h /^ unsigned int db : 1; \/\/ 0 = 16-bit segment, 1 = 32-bit segment$/;" m struct:gdt_seg
+g gdt.h /^ unsigned int g : 1; \/\/ Granularity: limit scaled by 4K when set$/;" m struct:gdt_seg
+gate_desc interrupt.h /^struct gate_desc$/;" s
+gdt_code_seg interrupt.h /^ uint16_t gdt_code_seg; \/\/ The address of the code segment descriptor$/;" m struct:gate_desc
+gdt_seg gdt.h /^struct gdt_seg$/;" s
+handle_int interrupt.c /^uint32_t handle_int(uint8_t int_number, uint32_t esp)$/;" f
+handle_irq interrupt_stub.s /^handle_irq 0x00$/;" l
+handle_irq interrupt_stub.s /^handle_irq 0x01$/;" l
+handler_high interrupt.h /^ uint16_t handler_high; \/\/ The high bits of the address of the handler$/;" m struct:gate_desc
+handler_low interrupt.h /^ uint16_t handler_low; \/\/ The low bits of the address of the handler$/;" m struct:gate_desc
+hex string.c /^char *hex(uint32_t n, char *buf)$/;" f
+idt interrupt.h /^struct gate_desc idt[256];$/;" v typeref:struct:gate_desc
+idt_pointer interrupt.h /^struct idt_pointer$/;" s
+init_gdt_seg gdt.c /^struct gdt_seg *init_gdt_seg(struct gdt_seg *g, uint32_t base, uint32_t limit, uint8_t flags)$/;" f
+init_kernel_gdt gdt.c /^struct kernel_gdt *init_kernel_gdt()$/;" f
+int_bottom interrupt_stub.s /^int_bottom:$/;" l
+int_ignore interrupt_stub.s /^int_ignore:$/;" l
+int_number interrupt_stub.s /^ int_number: .byte 0$/;" l
+interrupt_init interrupt.c /^void interrupt_init(const struct kernel_gdt *g)$/;" f
+kernel_gdt gdt.h /^struct kernel_gdt$/;" s
+kernel_main main.c /^void kernel_main(void)$/;" f
+kgdt gdt.c /^struct kernel_gdt kgdt;$/;" v typeref:struct:kernel_gdt
+lim_15_0 gdt.h /^ unsigned int lim_15_0 : 16; \/\/ Low bits of segment limit$/;" m struct:gdt_seg
+lim_19_16 gdt.h /^ unsigned int lim_19_16 : 4; \/\/ High bits of segment limit$/;" m struct:gdt_seg
+load_kernel_gdt gdt.c /^void __attribute__((optimize("O0"))) load_kernel_gdt(struct kernel_gdt *g)$/;" f
+memcmp string.c /^int memcmp(void *s1, const void *s2, size_t n)$/;" f
+memcpy string.c /^void *memcpy(void *dest, const void *src, size_t n)$/;" f
+memmove string.c /^void *memmove(void *dest, const void *src, size_t n)$/;" f
+memset string.c /^void *memset(void *s, int c, size_t n) $/;" f
+null gdt.h /^ struct gdt_seg null;$/;" m struct:kernel_gdt typeref:struct:kernel_gdt::gdt_seg
+pic_master_command interrupt.c /^struct port pic_master_command = {0x20, port8bit_slow};$/;" v typeref:struct:port
+pic_master_command interrupt.h /^struct port pic_master_command;$/;" v typeref:struct:port
+pic_master_data interrupt.c /^struct port pic_master_data = {0x21, port8bit_slow};$/;" v typeref:struct:port
+pic_master_data interrupt.h /^struct port pic_master_data;$/;" v typeref:struct:port
+pic_slave_command interrupt.c /^struct port pic_slave_command = {0xA0, port8bit_slow};$/;" v typeref:struct:port
+pic_slave_command interrupt.h /^struct port pic_slave_command;$/;" v typeref:struct:port
+pic_slave_data interrupt.c /^struct port pic_slave_data = {0xA1, port8bit_slow}; $/;" v typeref:struct:port
+pic_slave_data interrupt.h /^struct port pic_slave_data; $/;" v typeref:struct:port
+port port.h /^struct port$/;" s
+port16bit port.h /^ port16bit, port32bit} bandwidth;$/;" e enum:port::__anon1
+port32bit port.h /^ port16bit, port32bit} bandwidth;$/;" e enum:port::__anon1
+port8bit port.h /^ enum { port8bit, port8bit_slow,$/;" e enum:port::__anon1
+port8bit_slow port.h /^ enum { port8bit, port8bit_slow,$/;" e enum:port::__anon1
+port_number port.h /^ uint16_t port_number;$/;" m struct:port
+port_read port.c /^uint32_t port_read(struct port p)$/;" f
+port_write port.c /^void port_write(struct port p, uint32_t data)$/;" f
+present gdt.h /^ unsigned int present : 1; \/\/ Present$/;" m struct:gdt_seg
+priv gdt.h /^ unsigned int priv : 2; \/\/ Descriptor Privilege Level$/;" m struct:gdt_seg
+reserved gdt.h /^ unsigned int reserved : 1; \/\/ Reserved$/;" m struct:gdt_seg
+reserved interrupt.h /^ uint8_t reserved; \/\/ We don't use this apparently$/;" m struct:gate_desc
+s gdt.h /^ unsigned int s : 1; \/\/ 0 = system, 1 = application$/;" m struct:gdt_seg
+set_int_desc_entry interrupt.c /^void set_int_desc_entry(uint8_t int_number, uint16_t gdt_code_seg,$/;" f
+size interrupt.h /^ uint16_t size;$/;" m struct:idt_pointer
+spin main.c /^void spin()$/;" f
+stack_bottom boot.s /^stack_bottom:$/;" l
+stack_top boot.s /^stack_top:$/;" l
+start_interrupts interrupt.c /^void start_interrupts()$/;" f
+strlen string.c /^size_t strlen(const char *s)$/;" f
+terminal_buffer vga.h /^uint16_t* terminal_buffer;$/;" v
+terminal_color vga.h /^uint8_t terminal_color;$/;" v
+terminal_column vga.h /^size_t terminal_column;$/;" v
+terminal_row vga.h /^size_t terminal_row;$/;" v
+type gdt.h /^ unsigned int type : 4; \/\/ Segment type $/;" m struct:gdt_seg
+unused gdt.h /^ unsigned int unused : 1; \/\/ Unused (available for software use)$/;" m struct:gdt_seg
+vga_black vga.h /^ vga_black,$/;" e enum:vga_color
+vga_blue vga.h /^ vga_blue,$/;" e enum:vga_color
+vga_brown vga.h /^ vga_brown,$/;" e enum:vga_color
+vga_color vga.c /^uint8_t vga_color(enum vga_color fg, enum vga_color bg)$/;" f
+vga_color vga.h /^enum vga_color {$/;" g
+vga_cyan vga.h /^ vga_cyan,$/;" e enum:vga_color
+vga_dgray vga.h /^ vga_dgray,$/;" e enum:vga_color
+vga_entry vga.c /^uint16_t vga_entry(unsigned char uc, uint8_t color)$/;" f
+vga_green vga.h /^ vga_green,$/;" e enum:vga_color
+vga_lblue vga.h /^ vga_lblue,$/;" e enum:vga_color
+vga_lbrown vga.h /^ vga_lbrown,$/;" e enum:vga_color
+vga_lcyan vga.h /^ vga_lcyan,$/;" e enum:vga_color
+vga_lgray vga.h /^ vga_lgray,$/;" e enum:vga_color
+vga_lgreen vga.h /^ vga_lgreen,$/;" e enum:vga_color
+vga_lmagenta vga.h /^ vga_lmagenta,$/;" e enum:vga_color
+vga_lred vga.h /^ vga_lred,$/;" e enum:vga_color
+vga_magenta vga.h /^ vga_magenta,$/;" e enum:vga_color
+vga_red vga.h /^ vga_red,$/;" e enum:vga_color
+vga_white vga.h /^ vga_white$/;" e enum:vga_color
+vinit vga.c /^void vinit(void)$/;" f
+vlocate vga.c /^void vlocate(size_t column, size_t row)$/;" f
+vprint vga.c /^void vprint(const char* str)$/;" f
+vputchar vga.c /^void vputchar(char c)$/;" f
+vscroll vga.c /^void vscroll()$/;" f
+vsetchar vga.c /^void vsetchar(char c, uint8_t color, size_t x, size_t y)$/;" f
+vsetcolor vga.c /^void vsetcolor(uint8_t color)$/;" f
diff --git a/src/bees.c b/src/bees.c
new file mode 100644
index 0000000..7c2fbcb
--- /dev/null
+++ b/src/bees.c
@@ -0,0 +1,22 @@
+
+#include "vga.h"
+#include "printf.h"
+
+const char *bees_msg =
+"\n\n\n"
+" / \\ the number of bees generated by citrons os has exceeded the" "\n"
+" / \\ maximum amount your computer is capable of handling." "\n"
+" \\ -- /" "\n"
+" _====_ your computer will now halt." "\n"
+" / |--| \\" "\n"
+" / /==\\ \\ error: %s" "\n"
+" / /----\\ \\" "\n"
+" / /======\\ \\" "\n"
+" \\_/ ---- \\_/" "\n";
+
+void bees(const char *error) {
+ vsetcolor(vga_color(vga_white, vga_blue));
+ vclear();
+ printf(bees_msg, error);
+ while (1) asm("cli;hlt");
+}
diff --git a/src/bees.h b/src/bees.h
new file mode 100644
index 0000000..0e5b1d2
--- /dev/null
+++ b/src/bees.h
@@ -0,0 +1,6 @@
+#ifndef __BUGCHECK_H
+#define __BUGCHECK_H
+
+void bees(const char *error);
+
+#endif
diff --git a/src/boot.s b/src/boot.s
new file mode 100644
index 0000000..71017ff
--- /dev/null
+++ b/src/boot.s
@@ -0,0 +1,80 @@
+/* Parameters for the multiboot header */
+.set ALIGN, 1<<0
+.set MEMINFO, 1<<1
+.set FLAGS, ALIGN | MEMINFO
+.set MAGIC, 0x1BADB002
+.set CHECKSUM, -(MAGIC + FLAGS)
+
+.section .multiboot
+.align 4
+.long MAGIC
+.long FLAGS
+.long CHECKSUM
+
+
+/* Allocate room for stack */
+.section bss
+.align 16
+stack_bottom:
+.skip 16384 /* 16 KiB */
+stack_top:
+
+.section .data
+
+.intel_syntax noprefix
+gdt:
+ null_gdt: /* null descriptor */
+ .quad 0x00
+
+ code_gdt:
+ .word 0xFFFF /* limit */
+ .word 0x0000 /* base */
+ .byte 0x00 /* base */
+ .byte 0x9A /* type flag (access) */
+ .byte 0b11001111 /* limit */
+ .byte 0x00 /* base */
+
+ data_gdt:
+ .word 0xFFFF /* limit */
+ .word 0x0000 /* base */
+ .byte 0x00 /* base */
+ .byte 0x92 /* type flag (access) */
+ .byte 0b11001111 /* limit */
+ .byte 0x00 /* base */
+
+ end_gdt:
+
+ gdt_desc:
+ .word end_gdt - gdt - 1
+ .int gdt
+
+.att_syntax
+
+.section .text
+.global _start
+.type _start, @function
+_start:
+ mov $stack_top, %esp
+
+ cli
+
+.intel_syntax noprefix
+ lgdt [gdt_desc] /* load GDT */
+ mov ax, 0x10
+ mov ds, ax
+ mov ss, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+ jmp 0x08:continue
+.att_syntax
+
+continue:
+ call kernel_main
+
+ cli
+1: hlt
+ jmp 1b
+
+/* The size of the _start symbol is the current location '.' minus its start. */
+.size _start, . - _start
diff --git a/src/interrupt.c b/src/interrupt.c
new file mode 100644
index 0000000..e588353
--- /dev/null
+++ b/src/interrupt.c
@@ -0,0 +1,92 @@
+
+#include "interrupt.h"
+#include "string.h"
+#include "vga.h"
+#include "bees.h"
+#include "printf.h"
+#include "keyboard.h"
+
+struct port pic_master_command = {0x20, port8bit_slow};
+struct port pic_master_data = {0x21, port8bit_slow};
+struct port pic_slave_command = {0xA0, port8bit_slow};
+struct port pic_slave_data = {0xA1, port8bit_slow};
+
+struct port ps2_port = {0x60, port8bit};
+
+struct gate_desc idt[256] = {0};
+
+#define PIC_EOI 0x20
+
+void irq_end(uint8_t int_number) {
+ if (int_number >= 0x28)
+ port_write(pic_slave_command, PIC_EOI);
+ port_write(pic_master_command, PIC_EOI);
+}
+
+uint32_t handle_int(uint8_t int_number, uint32_t idk, uint32_t esp) {
+ switch (int_number) {
+ case 0x0D:
+ // bye
+ bees("general protection fault");
+ break;
+ case 0x21:
+ process_scancode(port_read(ps2_port));
+ irq_end(int_number);
+ break;
+ }
+
+ return esp;
+}
+
+void set_int_desc_entry(uint8_t int_number, uint16_t gdt_code_seg,
+ void (*handler)(), uint8_t privilege_level,
+ uint8_t desc_type) {
+ const uint8_t IDT_DESC_PRESENT = 0x80;
+
+ idt[int_number].handler_low = ((uint32_t) handler) & 0xFFFF;
+ idt[int_number].handler_high = ((uint32_t) handler >> 16) & 0xFFFF;
+ idt[int_number].gdt_code_seg = gdt_code_seg;
+ idt[int_number].access = IDT_DESC_PRESENT | desc_type | ((privilege_level&3) << 5);
+ idt[int_number].reserved = 0;
+}
+
+void interrupt_init() {
+ uint16_t code_seg = 0x08;
+ const uint8_t IDT_INTERRUPT_GATE = 0xE;
+
+ for (uint16_t i = 0; i < 256; i++)
+ set_int_desc_entry(i, code_seg, &int_ignore, 0, IDT_INTERRUPT_GATE);
+
+ // Put the interrupt handlers in the IDT
+ set_int_desc_entry(0x20, code_seg, &handle_irq0x00, 0, IDT_INTERRUPT_GATE);
+ set_int_desc_entry(0x21, code_seg, &handle_irq0x01, 0, IDT_INTERRUPT_GATE);
+ set_int_desc_entry(0x0D, code_seg, &handle_irq0x0D, 0, IDT_INTERRUPT_GATE);
+
+ port_write(pic_master_command, 0x11);
+ port_write(pic_slave_command, 0x11);
+
+ // (I actually don't understand how this works)
+ // Remap the PIC
+ port_write(pic_master_data, 0x20); // Interrupt vector offsets
+ port_write(pic_slave_data, 0x28);
+
+ port_write(pic_master_data, 0x04); // There is a slave PIC at IRQ2
+ port_write(pic_slave_data, 0x02); // Slave PIC cascade identity
+
+ port_write(pic_master_data, 0x01);
+ port_write(pic_slave_data, 0x01);
+
+ port_write(pic_master_data, 0xFD);
+ port_write(pic_slave_data, 0xFF);
+
+ // Load the IDT
+ struct idt_pointer i;
+ i.size = 256 * sizeof(struct gate_desc) - 1;
+ i.base = (uint32_t) idt;
+
+ asm volatile("lidt %0" : : "m" (i));
+}
+
+void start_interrupts() {
+ asm("sti");
+}
diff --git a/src/interrupt.h b/src/interrupt.h
new file mode 100644
index 0000000..5e0112b
--- /dev/null
+++ b/src/interrupt.h
@@ -0,0 +1,46 @@
+
+#ifndef __INTERRUPT_H
+#define __INTERRUPT_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "vga.h"
+#include "port.h"
+
+struct gate_desc
+{
+ uint16_t handler_low; // the low bits of the address of the handler
+ uint16_t gdt_code_seg; // the address of the code segment descriptor
+ uint8_t reserved; // we don't use this apparently
+ uint8_t access; // access rights
+ uint16_t handler_high; // the high bits of the address of the handler
+} __attribute__((packed));
+
+struct gate_desc idt[256];
+
+struct idt_pointer
+{
+ uint16_t size;
+ uint32_t base;
+} __attribute__((packed));
+
+void set_int_desc_entry(uint8_t int_number, uint16_t gdt_code_seg,
+ void (*handler)(), uint8_t privlege_level,
+ uint8_t desc_type);
+
+void interrupt_init();
+
+void start_interrupts();
+
+void handle_irq0x00();
+void handle_irq0x01();
+void handle_irq0x0D();
+
+void int_ignore();
+
+struct port pic_master_command;
+struct port pic_master_data;
+struct port pic_slave_command;
+struct port pic_slave_data;
+
+#endif
diff --git a/src/interrupt_stub.s b/src/interrupt_stub.s
new file mode 100644
index 0000000..618c982
--- /dev/null
+++ b/src/interrupt_stub.s
@@ -0,0 +1,54 @@
+
+.set IRQ_BASE, 0x20
+
+.section .text
+
+.extern handle_int
+.global int_ignore
+
+
+.macro handle_exception num
+.global handle_irq\num
+handle_irq\num:
+ movb $\num, (int_number)
+ jmp int_bottom
+.endm
+
+.macro handle_irq num
+.global handle_irq\num
+handle_irq\num:
+ movb $\num + IRQ_BASE, (int_number)
+ jmp int_bottom
+.endm
+
+handle_irq 0x00
+handle_irq 0x01
+handle_exception 0x0D
+
+int_bottom:
+ pop %eax
+ push int_bottom
+ pusha
+ pushl %ds
+ pushl %es
+ pushl %fs
+ pushl %gs
+
+ pushl %esp
+ pushl %eax
+ push (int_number)
+ call handle_int
+ movl %eax, %esp // Set the stack pointer to the return value of handle_int
+
+ popl %gs
+ popl %fs
+ popl %es
+ popl %ds
+ popa
+
+int_ignore:
+
+ iret
+
+.data
+ int_number: .byte 0
diff --git a/src/keyboard.c b/src/keyboard.c
new file mode 100644
index 0000000..30dc5b7
--- /dev/null
+++ b/src/keyboard.c
@@ -0,0 +1,98 @@
+
+#include <stdint.h>
+#include "vga.h"
+
+enum special_key {
+ K_ESCAPE = 0x1b,
+ K_BACKSPACE = 0x08,
+ K_F1 = 0x0E,
+ K_F2 = 0x0F,
+ K_F3 = 0x10,
+ K_F4 = 0x11,
+ K_F5 = 0x12,
+ K_F6 = 0x13,
+ K_F7 = 0x14,
+ K_F8 = 0x15,
+ K_F9 = 0x16,
+ K_F10 = 0x17,
+ K_F11 = 0x18,
+ K_F12 = 0x19
+};
+
+enum mod_scancodes {
+ SC_LSHIFT = 0x2A,
+ SC_RSHIFT = 0x36,
+ SC_LCTRL = 0x1D,
+ SC_LALT = 0x38,
+ SC_META = 0x3A
+};
+
+char keymap_unshift[] = {
+ 0,K_ESCAPE,'1','2','3','4','5','6','7','8','9','0','-','=',K_BACKSPACE,
+ '\t','q','w','e','r','t','y','u','i','o','p','[',']','\n',0,
+ 'a','s','d','f','g','h','j','k','l',';','\'','`',0,'\\',
+ 'z','x','c','v','b','n','m',',','.','/',0,'*',0,' ',0,
+ K_F1,K_F2,K_F3,K_F4,K_F5,K_F6,K_F7,K_F8,K_F9,K_F10,0,0,
+ '7','8','9','-','4','5','6','1','2','3','0','.',0,0,0,K_F11,K_F12
+};
+char keymap_shift[] = {
+ 0,K_ESCAPE,'!','@','#','$','%','^','&','*','(',')','_','+',K_BACKSPACE,
+ '\t','Q','W','E','R','T','Y','U','I','O','P','{','}','\n',0,
+ 'A','S','D','F','G','H','J','K','L',':','"','~',0,'|',
+ 'Z','X','C','V','B','N','M','<','>','?',0,'*',0,' ',0,
+ K_F1,K_F2,K_F3,K_F4,K_F5,K_F6,K_F7,K_F8,K_F9,K_F10,0,0,
+ '7','8','9','-','4','5','6','1','2','3','0','.',0,0,0,K_F11,K_F12
+};
+
+char *keymap = keymap_unshift;
+
+struct {
+ int shift;
+ int ctrl;
+ int alt;
+ int meta;
+} mod_keys = {0};
+
+void keydown(char c) {
+ vputchar(c);
+}
+
+void keyup(char c) {
+
+}
+
+void process_scancode(uint8_t c) {
+ uint8_t code;
+ int release;
+ if (c > 0x80 && c <= 0xD8) {
+ release = 1;
+ code = c - 0x80;
+ } else {
+ release = 0;
+ code = c;
+ }
+ switch (code) {
+ default:
+ if (code <= 0x58 && keymap[code] != 0) {
+ if (!release)
+ keydown(keymap[code]);
+ else
+ keyup(keymap[code]);
+ }
+ break;
+ case SC_LSHIFT:
+ case SC_RSHIFT:
+ mod_keys.shift = !release;
+ keymap = release ? keymap_unshift : keymap_shift;
+ break;
+ case SC_LCTRL:
+ mod_keys.ctrl = !release;
+ break;
+ case SC_LALT:
+ mod_keys.alt = !release;
+ break;
+ case SC_META:
+ mod_keys.meta = !release;
+ break;
+ }
+}
diff --git a/src/keyboard.h b/src/keyboard.h
new file mode 100644
index 0000000..c867e07
--- /dev/null
+++ b/src/keyboard.h
@@ -0,0 +1,15 @@
+#ifndef __KEYBOARD_H
+#define __KEYBOARD_H
+
+#include <stdint.h>
+
+void process_scancode(uint8_t c);
+
+struct {
+ int shift;
+ int ctrl;
+ int alt;
+ int meta;
+} mod_keys;
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..725f438
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,45 @@
+
+#define KERNEL
+
+#include "vga.h"
+#include "interrupt.h"
+#include "string.h"
+
+extern void *gdt;
+
+const char *pope =
+"The pope, also known as the supreme pontiff (Pontifex maximus) or the Roman \n"
+"pontiff (Romanus Pontifex), is the bishop of Rome, chief pastor of the \n"
+"worldwide Catholic Church,[4] and head of state or sovereign of the Vatican \n"
+"City State.[5] The primacy of the bishop of Rome is largely derived from his \n"
+"role as the apostolic successor to Saint Peter, to whom primacy was conferred \n"
+"by Jesus, giving him the Keys of Heaven and the powers of \"binding and \n"
+"loosing\", naming him as the \"rock\" upon which the church would be built. \n"
+"Since 1929, the pope has official residence in the Apostolic Palace in the \n"
+"Vatican City, a city-state enclaved within Rome, Italy.[6] The current pope is \n"
+"Francis, who was elected on 13 March 2013, succeeding Benedict XVI.[7] \n"
+"While his office is called the papacy, the jurisdiction of the episcopal see is\n"
+"called the Holy See.[8] It is the Holy See that is the sovereign entity by \n"
+"international law headquartered in the distinctively independent Vatican City \n"
+"State, established by the Lateran Treaty in 1929 between Italy and the Holy See\n"
+"to ensure its temporal, diplomatic, and spiritual independence. The Holy See is\n"
+"recognized by its adherence at various levels to international organization and\n"
+"by means of its diplomatic relations and political accords with many \n"
+"independent states.\n";
+
+void kernel_main(void) {
+ vsetcolor(vga_color(vga_black, vga_white));
+ vclear();
+ interrupt_init();
+ start_interrupts();
+
+ vprint(" _ _ \n");
+ vprint(" ___(_) |_ _ __ ___ _ __ ___ ___ ___ \n");
+ vprint(" / __| | __| '__/ _ \\| '_ \\/ __| / _ \\/ __|\n");
+ vprint("| (__| | |_| | | (_) | | | \\__ \\ | (_) \\__ \n");
+ vprint(" \\___|_|\\__|_| \\___/|_| |_|___/ \\___/|___/\n");
+
+ vprint(pope);
+
+ while (1) asm("hlt");
+}
diff --git a/src/port.c b/src/port.c
new file mode 100644
index 0000000..9af4a49
--- /dev/null
+++ b/src/port.c
@@ -0,0 +1,38 @@
+
+#include "port.h"
+
+void port_write(struct port p, uint32_t data) {
+ switch (p.bandwidth) {
+ case port8bit:
+ asm volatile("outb %0, %1" : : "a" ((uint8_t) data), "Nd" (p.port_number));
+ break;
+ case port8bit_slow:
+ asm volatile("outb %0, %1\njmp 1f\n1: jmp 1f\n1:" :
+ : "a" ((uint8_t) data), "Nd" (p.port_number));
+ break;
+ case port16bit:
+ asm volatile("outw %0, %1" : : "a" ((uint16_t) data), "Nd" (p.port_number));
+ case port32bit:
+ asm volatile("outl %0, %1" : : "a" (data), "Nd" (p.port_number));
+ break;
+ }
+}
+
+uint32_t port_read(struct port p) {
+ uint32_t result = 0;
+ switch (p.bandwidth) {
+ case port8bit:
+ asm volatile("inb %1, %0" : "=a" (result) : "Nd" (p.port_number));
+ break;
+ case port8bit_slow:
+ asm volatile("inb %1, %0\njmp 1f\n1: jmp 1f\n1:" : "=a" (result) : "Nd" (p.port_number));
+ break;
+ case port16bit:
+ asm volatile("inw %1, %0" : "=a" (result) : "Nd" (p.port_number));
+ break;
+ case port32bit:
+ asm volatile("inl %1, %0" : "=a" (result) : "Nd" (p.port_number));
+ }
+
+ return result;
+}
diff --git a/src/port.h b/src/port.h
new file mode 100644
index 0000000..c7ed457
--- /dev/null
+++ b/src/port.h
@@ -0,0 +1,19 @@
+
+#ifndef __PORT_H
+#define __PORT_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct port
+{
+ uint16_t port_number;
+ enum { port8bit, port8bit_slow,
+ port16bit, port32bit} bandwidth;
+};
+
+void port_write(struct port p, uint32_t data);
+uint32_t port_read(struct port p);
+
+
+#endif
diff --git a/src/printf.c b/src/printf.c
new file mode 100644
index 0000000..8a700ad
--- /dev/null
+++ b/src/printf.c
@@ -0,0 +1,914 @@
+///////////////////////////////////////////////////////////////////////////////
+// \author (c) Marco Paland (info@paland.com)
+// 2014-2019, PALANDesign Hannover, Germany
+//
+// \license The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
+// embedded systems with a very limited resources. These routines are thread
+// safe and reentrant!
+// Use this instead of the bloated standard/newlib printf cause these use
+// malloc for printf (and may not be thread safe).
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "printf.h"
+
+
+// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
+// printf_config.h header file
+// default: undefined
+#ifdef PRINTF_INCLUDE_CONFIG_H
+#include "printf_config.h"
+#endif
+
+
+// 'ntoa' conversion buffer size, this must be big enough to hold one converted
+// numeric number including padded zeros (dynamically created on stack)
+// default: 32 byte
+#ifndef PRINTF_NTOA_BUFFER_SIZE
+#define PRINTF_NTOA_BUFFER_SIZE 32U
+#endif
+
+// 'ftoa' conversion buffer size, this must be big enough to hold one converted
+// float number including padded zeros (dynamically created on stack)
+// default: 32 byte
+#ifndef PRINTF_FTOA_BUFFER_SIZE
+#define PRINTF_FTOA_BUFFER_SIZE 32U
+#endif
+
+// support for the floating point type (%f)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
+#define PRINTF_SUPPORT_FLOAT
+#endif
+
+// support for exponential floating point notation (%e/%g)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
+#define PRINTF_SUPPORT_EXPONENTIAL
+#endif
+
+// define the default floating point precision
+// default: 6 digits
+#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
+#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
+#endif
+
+// define the largest float suitable to print with %f
+// default: 1e9
+#ifndef PRINTF_MAX_FLOAT
+#define PRINTF_MAX_FLOAT 1e9
+#endif
+
+// support for the long long types (%llu or %p)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
+#define PRINTF_SUPPORT_LONG_LONG
+#endif
+
+// support for the ptrdiff_t type (%t)
+// ptrdiff_t is normally defined in <stddef.h> as long or long long type
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
+#define PRINTF_SUPPORT_PTRDIFF_T
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// internal flag definitions
+#define FLAGS_ZEROPAD (1U << 0U)
+#define FLAGS_LEFT (1U << 1U)
+#define FLAGS_PLUS (1U << 2U)
+#define FLAGS_SPACE (1U << 3U)
+#define FLAGS_HASH (1U << 4U)
+#define FLAGS_UPPERCASE (1U << 5U)
+#define FLAGS_CHAR (1U << 6U)
+#define FLAGS_SHORT (1U << 7U)
+#define FLAGS_LONG (1U << 8U)
+#define FLAGS_LONG_LONG (1U << 9U)
+#define FLAGS_PRECISION (1U << 10U)
+#define FLAGS_ADAPT_EXP (1U << 11U)
+
+
+// import float.h for DBL_MAX
+#if defined(PRINTF_SUPPORT_FLOAT)
+#include <float.h>
+#endif
+
+
+// output function type
+typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
+
+
+// wrapper (used as buffer) for output function type
+typedef struct {
+ void (*fct)(char character, void* arg);
+ void* arg;
+} out_fct_wrap_type;
+
+
+// internal buffer output
+static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
+{
+ if (idx < maxlen) {
+ ((char*)buffer)[idx] = character;
+ }
+}
+
+
+// internal null output
+static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
+{
+ (void)character; (void)buffer; (void)idx; (void)maxlen;
+}
+
+
+// internal _putchar wrapper
+static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
+{
+ (void)buffer; (void)idx; (void)maxlen;
+ if (character) {
+ _putchar(character);
+ }
+}
+
+
+// internal output function wrapper
+static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
+{
+ (void)idx; (void)maxlen;
+ if (character) {
+ // buffer is the output fct pointer
+ ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
+ }
+}
+
+
+// internal secure strlen
+// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
+static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
+{
+ const char* s;
+ for (s = str; *s && maxsize--; ++s);
+ return (unsigned int)(s - str);
+}
+
+
+// internal test if char is a digit (0-9)
+// \return true if char is a digit
+static inline bool _is_digit(char ch)
+{
+ return (ch >= '0') && (ch <= '9');
+}
+
+
+// internal ASCII string to unsigned int conversion
+static unsigned int _atoi(const char** str)
+{
+ unsigned int i = 0U;
+ while (_is_digit(**str)) {
+ i = i * 10U + (unsigned int)(*((*str)++) - '0');
+ }
+ return i;
+}
+
+
+// output the specified string in reverse, taking care of any zero-padding
+static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
+{
+ const size_t start_idx = idx;
+
+ // pad spaces up to given width
+ if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
+ for (size_t i = len; i < width; i++) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+
+ // reverse string
+ while (len) {
+ out(buf[--len], buffer, idx++, maxlen);
+ }
+
+ // append pad spaces up to given width
+ if (flags & FLAGS_LEFT) {
+ while (idx - start_idx < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+
+ return idx;
+}
+
+
+// internal itoa format
+static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ // pad leading zeros
+ if (!(flags & FLAGS_LEFT)) {
+ if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
+ width--;
+ }
+ while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = '0';
+ }
+ while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = '0';
+ }
+ }
+
+ // handle hash
+ if (flags & FLAGS_HASH) {
+ if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
+ len--;
+ if (len && (base == 16U)) {
+ len--;
+ }
+ }
+ if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = 'x';
+ }
+ else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = 'X';
+ }
+ else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = 'b';
+ }
+ if (len < PRINTF_NTOA_BUFFER_SIZE) {
+ buf[len++] = '0';
+ }
+ }
+
+ if (len < PRINTF_NTOA_BUFFER_SIZE) {
+ if (negative) {
+ buf[len++] = '-';
+ }
+ else if (flags & FLAGS_PLUS) {
+ buf[len++] = '+'; // ignore the space if the '+' exists
+ }
+ else if (flags & FLAGS_SPACE) {
+ buf[len++] = ' ';
+ }
+ }
+
+ return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
+}
+
+
+// internal itoa for 'long' type
+static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ char buf[PRINTF_NTOA_BUFFER_SIZE];
+ size_t len = 0U;
+
+ // no hash for 0 values
+ if (!value) {
+ flags &= ~FLAGS_HASH;
+ }
+
+ // write if precision != 0 and value is != 0
+ if (!(flags & FLAGS_PRECISION) || value) {
+ do {
+ const char digit = (char)(value % base);
+ buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
+ value /= base;
+ } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
+ }
+
+ return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
+}
+
+
+// internal itoa for 'long long' type
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ char buf[PRINTF_NTOA_BUFFER_SIZE];
+ size_t len = 0U;
+
+ // no hash for 0 values
+ if (!value) {
+ flags &= ~FLAGS_HASH;
+ }
+
+ // write if precision != 0 and value is != 0
+ if (!(flags & FLAGS_PRECISION) || value) {
+ do {
+ const char digit = (char)(value % base);
+ buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
+ value /= base;
+ } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
+ }
+
+ return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
+}
+#endif // PRINTF_SUPPORT_LONG_LONG
+
+
+#if defined(PRINTF_SUPPORT_FLOAT)
+
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
+static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
+#endif
+
+
+// internal ftoa for fixed decimal floating point
+static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ char buf[PRINTF_FTOA_BUFFER_SIZE];
+ size_t len = 0U;
+ double diff = 0.0;
+
+ // powers of 10
+ static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
+
+ // test for special values
+ if (value != value)
+ return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
+ if (value < -DBL_MAX)
+ return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
+ if (value > DBL_MAX)
+ return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
+
+ // test for very large values
+ // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
+ if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+ return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
+#else
+ return 0U;
+#endif
+ }
+
+ // test for negative
+ bool negative = false;
+ if (value < 0) {
+ negative = true;
+ value = 0 - value;
+ }
+
+ // set default precision, if not set explicitly
+ if (!(flags & FLAGS_PRECISION)) {
+ prec = PRINTF_DEFAULT_FLOAT_PRECISION;
+ }
+ // limit precision to 9, cause a prec >= 10 can lead to overflow errors
+ while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
+ buf[len++] = '0';
+ prec--;
+ }
+
+ int whole = (int)value;
+ double tmp = (value - whole) * pow10[prec];
+ unsigned long frac = (unsigned long)tmp;
+ diff = tmp - frac;
+
+ if (diff > 0.5) {
+ ++frac;
+ // handle rollover, e.g. case 0.99 with prec 1 is 1.0
+ if (frac >= pow10[prec]) {
+ frac = 0;
+ ++whole;
+ }
+ }
+ else if (diff < 0.5) {
+ }
+ else if ((frac == 0U) || (frac & 1U)) {
+ // if halfway, round up if odd OR if last digit is 0
+ ++frac;
+ }
+
+ if (prec == 0U) {
+ diff = value - (double)whole;
+ if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
+ // exactly 0.5 and ODD, then round up
+ // 1.5 -> 2, but 2.5 -> 2
+ ++whole;
+ }
+ }
+ else {
+ unsigned int count = prec;
+ // now do fractional part, as an unsigned number
+ while (len < PRINTF_FTOA_BUFFER_SIZE) {
+ --count;
+ buf[len++] = (char)(48U + (frac % 10U));
+ if (!(frac /= 10U)) {
+ break;
+ }
+ }
+ // add extra 0s
+ while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
+ buf[len++] = '0';
+ }
+ if (len < PRINTF_FTOA_BUFFER_SIZE) {
+ // add decimal
+ buf[len++] = '.';
+ }
+ }
+
+ // do whole part, number is reversed
+ while (len < PRINTF_FTOA_BUFFER_SIZE) {
+ buf[len++] = (char)(48 + (whole % 10));
+ if (!(whole /= 10)) {
+ break;
+ }
+ }
+
+ // pad leading zeros
+ if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
+ if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
+ width--;
+ }
+ while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
+ buf[len++] = '0';
+ }
+ }
+
+ if (len < PRINTF_FTOA_BUFFER_SIZE) {
+ if (negative) {
+ buf[len++] = '-';
+ }
+ else if (flags & FLAGS_PLUS) {
+ buf[len++] = '+'; // ignore the space if the '+' exists
+ }
+ else if (flags & FLAGS_SPACE) {
+ buf[len++] = ' ';
+ }
+ }
+
+ return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
+}
+
+
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>
+static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ // check for NaN and special values
+ if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
+ return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
+ }
+
+ // determine the sign
+ const bool negative = value < 0;
+ if (negative) {
+ value = -value;
+ }
+
+ // default precision
+ if (!(flags & FLAGS_PRECISION)) {
+ prec = PRINTF_DEFAULT_FLOAT_PRECISION;
+ }
+
+ // determine the decimal exponent
+ // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
+ union {
+ uint64_t U;
+ double F;
+ } conv;
+
+ conv.F = value;
+ int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
+ conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
+ // now approximate log10 from the log2 integer part and an expansion of ln around 1.5
+ int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
+ // now we want to compute 10^expval but we want to be sure it won't overflow
+ exp2 = (int)(expval * 3.321928094887362 + 0.5);
+ const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
+ const double z2 = z * z;
+ conv.U = (uint64_t)(exp2 + 1023) << 52U;
+ // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
+ conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
+ // correct for rounding errors
+ if (value < conv.F) {
+ expval--;
+ conv.F /= 10;
+ }
+
+ // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
+ unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
+
+ // in "%g" mode, "prec" is the number of *significant figures* not decimals
+ if (flags & FLAGS_ADAPT_EXP) {
+ // do we want to fall-back to "%f" mode?
+ if ((value >= 1e-4) && (value < 1e6)) {
+ if ((int)prec > expval) {
+ prec = (unsigned)((int)prec - expval - 1);
+ }
+ else {
+ prec = 0;
+ }
+ flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
+ // no characters in exponent
+ minwidth = 0U;
+ expval = 0;
+ }
+ else {
+ // we use one sigfig for the whole part
+ if ((prec > 0) && (flags & FLAGS_PRECISION)) {
+ --prec;
+ }
+ }
+ }
+
+ // will everything fit?
+ unsigned int fwidth = width;
+ if (width > minwidth) {
+ // we didn't fall-back so subtract the characters required for the exponent
+ fwidth -= minwidth;
+ } else {
+ // not enough characters, so go back to default sizing
+ fwidth = 0U;
+ }
+ if ((flags & FLAGS_LEFT) && minwidth) {
+ // if we're padding on the right, DON'T pad the floating part
+ fwidth = 0U;
+ }
+
+ // rescale the float value
+ if (expval) {
+ value /= conv.F;
+ }
+
+ // output the floating part
+ const size_t start_idx = idx;
+ idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
+
+ // output the exponent part
+ if (minwidth) {
+ // output the exponential symbol
+ out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
+ // output the exponent value
+ idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
+ // might need to right-pad spaces
+ if (flags & FLAGS_LEFT) {
+ while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
+ }
+ }
+ return idx;
+}
+#endif // PRINTF_SUPPORT_EXPONENTIAL
+#endif // PRINTF_SUPPORT_FLOAT
+
+
+// internal vsnprintf
+static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
+{
+ unsigned int flags, width, precision, n;
+ size_t idx = 0U;
+
+ if (!buffer) {
+ // use null output function
+ out = _out_null;
+ }
+
+ while (*format)
+ {
+ // format specifier? %[flags][width][.precision][length]
+ if (*format != '%') {
+ // no
+ out(*format, buffer, idx++, maxlen);
+ format++;
+ continue;
+ }
+ else {
+ // yes, evaluate it
+ format++;
+ }
+
+ // evaluate flags
+ flags = 0U;
+ do {
+ switch (*format) {
+ case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
+ case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
+ case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
+ case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
+ case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
+ default : n = 0U; break;
+ }
+ } while (n);
+
+ // evaluate width field
+ width = 0U;
+ if (_is_digit(*format)) {
+ width = _atoi(&format);
+ }
+ else if (*format == '*') {
+ const int w = va_arg(va, int);
+ if (w < 0) {
+ flags |= FLAGS_LEFT; // reverse padding
+ width = (unsigned int)-w;
+ }
+ else {
+ width = (unsigned int)w;
+ }
+ format++;
+ }
+
+ // evaluate precision field
+ precision = 0U;
+ if (*format == '.') {
+ flags |= FLAGS_PRECISION;
+ format++;
+ if (_is_digit(*format)) {
+ precision = _atoi(&format);
+ }
+ else if (*format == '*') {
+ const int prec = (int)va_arg(va, int);
+ precision = prec > 0 ? (unsigned int)prec : 0U;
+ format++;
+ }
+ }
+
+ // evaluate length field
+ switch (*format) {
+ case 'l' :
+ flags |= FLAGS_LONG;
+ format++;
+ if (*format == 'l') {
+ flags |= FLAGS_LONG_LONG;
+ format++;
+ }
+ break;
+ case 'h' :
+ flags |= FLAGS_SHORT;
+ format++;
+ if (*format == 'h') {
+ flags |= FLAGS_CHAR;
+ format++;
+ }
+ break;
+#if defined(PRINTF_SUPPORT_PTRDIFF_T)
+ case 't' :
+ flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+ format++;
+ break;
+#endif
+ case 'j' :
+ flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+ format++;
+ break;
+ case 'z' :
+ flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+ format++;
+ break;
+ default :
+ break;
+ }
+
+ // evaluate specifier
+ switch (*format) {
+ case 'd' :
+ case 'i' :
+ case 'u' :
+ case 'x' :
+ case 'X' :
+ case 'o' :
+ case 'b' : {
+ // set the base
+ unsigned int base;
+ if (*format == 'x' || *format == 'X') {
+ base = 16U;
+ }
+ else if (*format == 'o') {
+ base = 8U;
+ }
+ else if (*format == 'b') {
+ base = 2U;
+ }
+ else {
+ base = 10U;
+ flags &= ~FLAGS_HASH; // no hash for dec format
+ }
+ // uppercase
+ if (*format == 'X') {
+ flags |= FLAGS_UPPERCASE;
+ }
+
+ // no plus or space flag for u, x, X, o, b
+ if ((*format != 'i') && (*format != 'd')) {
+ flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
+ }
+
+ // ignore '0' flag when precision is given
+ if (flags & FLAGS_PRECISION) {
+ flags &= ~FLAGS_ZEROPAD;
+ }
+
+ // convert the integer
+ if ((*format == 'i') || (*format == 'd')) {
+ // signed
+ if (flags & FLAGS_LONG_LONG) {
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+ const long long value = va_arg(va, long long);
+ idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
+#endif
+ }
+ else if (flags & FLAGS_LONG) {
+ const long value = va_arg(va, long);
+ idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
+ }
+ else {
+ const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
+ idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
+ }
+ }
+ else {
+ // unsigned
+ if (flags & FLAGS_LONG_LONG) {
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+ idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
+#endif
+ }
+ else if (flags & FLAGS_LONG) {
+ idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
+ }
+ else {
+ const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
+ idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
+ }
+ }
+ format++;
+ break;
+ }
+#if defined(PRINTF_SUPPORT_FLOAT)
+ case 'f' :
+ case 'F' :
+ if (*format == 'F') flags |= FLAGS_UPPERCASE;
+ idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
+ format++;
+ break;
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
+ if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
+ idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
+ format++;
+ break;
+#endif // PRINTF_SUPPORT_EXPONENTIAL
+#endif // PRINTF_SUPPORT_FLOAT
+ case 'c' : {
+ unsigned int l = 1U;
+ // pre padding
+ if (!(flags & FLAGS_LEFT)) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ // char output
+ out((char)va_arg(va, int), buffer, idx++, maxlen);
+ // post padding
+ if (flags & FLAGS_LEFT) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ format++;
+ break;
+ }
+
+ case 's' : {
+ const char* p = va_arg(va, char*);
+ unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
+ // pre padding
+ if (flags & FLAGS_PRECISION) {
+ l = (l < precision ? l : precision);
+ }
+ if (!(flags & FLAGS_LEFT)) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ // string output
+ while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
+ out(*(p++), buffer, idx++, maxlen);
+ }
+ // post padding
+ if (flags & FLAGS_LEFT) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ format++;
+ break;
+ }
+
+ case 'p' : {
+ width = sizeof(void*) * 2U;
+ flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+ const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
+ if (is_ll) {
+ idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
+ }
+ else {
+#endif
+ idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+ }
+#endif
+ format++;
+ break;
+ }
+
+ case '%' :
+ out('%', buffer, idx++, maxlen);
+ format++;
+ break;
+
+ default :
+ out(*format, buffer, idx++, maxlen);
+ format++;
+ break;
+ }
+ }
+
+ // termination
+ out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
+
+ // return written chars without terminating \0
+ return (int)idx;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+int printf_(const char* format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ char buffer[1];
+ const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
+ va_end(va);
+ return ret;
+}
+
+
+int sprintf_(char* buffer, const char* format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
+ va_end(va);
+ return ret;
+}
+
+
+int snprintf_(char* buffer, size_t count, const char* format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
+ va_end(va);
+ return ret;
+}
+
+
+int vprintf_(const char* format, va_list va)
+{
+ char buffer[1];
+ return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
+}
+
+
+int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
+{
+ return _vsnprintf(_out_buffer, buffer, count, format, va);
+}
+
+
+int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ const out_fct_wrap_type out_fct_wrap = { out, arg };
+ const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
+ va_end(va);
+ return ret;
+}
diff --git a/src/printf.h b/src/printf.h
new file mode 100644
index 0000000..6104ccf
--- /dev/null
+++ b/src/printf.h
@@ -0,0 +1,117 @@
+///////////////////////////////////////////////////////////////////////////////
+// \author (c) Marco Paland (info@paland.com)
+// 2014-2019, PALANDesign Hannover, Germany
+//
+// \license The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
+// embedded systems with a very limited resources.
+// Use this instead of bloated standard/newlib printf.
+// These routines are thread safe and reentrant.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _PRINTF_H_
+#define _PRINTF_H_
+
+#include <stdarg.h>
+#include <stddef.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * Output a character to a custom device like UART, used by the printf() function
+ * This function is declared here only. You have to write your custom implementation somewhere
+ * \param character Character to output
+ */
+void _putchar(char character);
+
+
+/**
+ * Tiny printf implementation
+ * You have to implement _putchar if you use printf()
+ * To avoid conflicts with the regular printf() API it is overridden by macro defines
+ * and internal underscore-appended functions like printf_() are used
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are written into the array, not counting the terminating null character
+ */
+#define printf printf_
+int printf_(const char* format, ...);
+
+
+/**
+ * Tiny sprintf implementation
+ * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
+ * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
+ */
+#define sprintf sprintf_
+int sprintf_(char* buffer, const char* format, ...);
+
+
+/**
+ * Tiny snprintf/vsnprintf implementation
+ * \param buffer A pointer to the buffer where to store the formatted string
+ * \param count The maximum number of characters to store in the buffer, including a terminating null character
+ * \param format A string that specifies the format of the output
+ * \param va A value identifying a variable arguments list
+ * \return The number of characters that COULD have been written into the buffer, not counting the terminating
+ * null character. A value equal or larger than count indicates truncation. Only when the returned value
+ * is non-negative and less than count, the string has been completely written.
+ */
+#define snprintf snprintf_
+#define vsnprintf vsnprintf_
+int snprintf_(char* buffer, size_t count, const char* format, ...);
+int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
+
+
+/**
+ * Tiny vprintf implementation
+ * \param format A string that specifies the format of the output
+ * \param va A value identifying a variable arguments list
+ * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
+ */
+#define vprintf vprintf_
+int vprintf_(const char* format, va_list va);
+
+
+/**
+ * printf with output function
+ * You may use this as dynamic alternative to printf() with its fixed _putchar() output
+ * \param out An output function which takes one character and an argument pointer
+ * \param arg An argument pointer for user data passed to output function
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are sent to the output function, not counting the terminating null character
+ */
+int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif // _PRINTF_H_
diff --git a/src/string.c b/src/string.c
new file mode 100644
index 0000000..f1876f2
--- /dev/null
+++ b/src/string.c
@@ -0,0 +1,64 @@
+
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+int memcmp(void *s1, const void *s2, size_t n)
+{
+ unsigned char *a = (unsigned char *) s1;
+ unsigned char *b = (unsigned char *) s2;
+ for (size_t i=0;i<n;i++)
+ if (a[i] != b[i]) return a[i] - b[i];
+ return 0;
+}
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+ unsigned char *d = (unsigned char *) dest;
+ unsigned char *s = (unsigned char *) src;
+ for (size_t i=0;i<n;i++) d[i] = s[i];
+ return dest;
+}
+
+void *memmove(void *dest, const void *src, size_t n)
+{
+ unsigned char *d = (unsigned char *) dest;
+ unsigned char *s = (unsigned char *) src;
+ if (d < s)
+ for (size_t i=0;i<n;i++)
+ d[i] = s[i];
+ else
+ for (size_t i=n;i!=0;i--)
+ d[i-1] = s[i-1];
+ return dest;
+}
+
+void *memset(void *s, int c, size_t n)
+{
+ unsigned char *str = (unsigned char *) s;
+ for (size_t i=0;i<n;i++) str[i] = c;
+ return s;
+}
+
+size_t strlen(const char *s)
+{
+ size_t len = 0;
+ while (s[++len]);
+ return len;
+}
+
+
+char *hex(uint32_t n, char *buf)
+{
+ buf[0] = '0'; buf[1] = 'x';
+ for (int i=0;i<8;i++)
+ {
+ int digit = (n >> (i * 4)) & 0xF;
+ if (digit < 10)
+ buf[9 - i] = '0' + digit;
+ else
+ buf[9 - i] = 'A' + (digit - 10);
+ }
+ return buf;
+}
diff --git a/src/string.h b/src/string.h
new file mode 100644
index 0000000..0d2c8d4
--- /dev/null
+++ b/src/string.h
@@ -0,0 +1,15 @@
+
+#ifndef STRING_H
+#define STRING_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+int memcmp(const void *s1, const void *s2, size_t n);
+void *memcpy(void *dest, const void *src, size_t n);
+void *memmove(void *dest, const void *src, size_t n);
+void *memset(void *s, int c, size_t n);
+size_t strlen(const char *s);
+char *hex(uint32_t n, char *buf);
+
+#endif
diff --git a/src/vga.c b/src/vga.c
new file mode 100644
index 0000000..3ff8342
--- /dev/null
+++ b/src/vga.c
@@ -0,0 +1,103 @@
+
+#include "vga.h"
+#include "string.h"
+#include "port.h"
+#include <stdint.h>
+#include <stddef.h>
+
+uint16_t* terminal_buffer = (uint16_t*) 0xB8000;;
+
+uint8_t vga_color(enum vga_color fg, enum vga_color bg) {
+ return fg | bg << 4;
+}
+
+uint16_t vga_entry(unsigned char uc, uint8_t color) {
+ return (uint16_t) uc | (uint16_t) color << 8;
+}
+
+void vclear(void) {
+ terminal_row = 0;
+ terminal_column = 0;
+ for (size_t y = 0; y < VGA_HEIGHT; y++)
+ {
+ for (size_t x = 0; x < VGA_WIDTH; x++)
+ {
+ const size_t index = y * VGA_WIDTH + x;
+ terminal_buffer[index] = vga_entry(' ', terminal_color);
+ }
+ }
+}
+
+void vsetcolor(uint8_t color) {
+ terminal_color = color;
+}
+
+void vsetchar(char c, uint8_t color, size_t x, size_t y) {
+ const size_t index = y * VGA_WIDTH + x;
+ terminal_buffer[index] = vga_entry(c, color);
+}
+
+void vscroll() {
+ for (unsigned int row=1; row < VGA_HEIGHT; row++)
+ {
+ size_t row_bytesize = VGA_WIDTH * 2; // A VGA entry is 2 bytes
+ // Copy each line one line up
+ memcpy(&terminal_buffer[(row - 1) * VGA_WIDTH],
+ &terminal_buffer[row * VGA_WIDTH], row_bytesize);
+ }
+ // Clear last line
+ for (unsigned int col=0; col < VGA_WIDTH; col++)
+ {
+ terminal_buffer[VGA_WIDTH * (VGA_HEIGHT - 1) + col] = vga_entry(' ', terminal_color);
+ }
+ terminal_row--;
+}
+
+void vputchar(char c) {
+ void newline() {
+ terminal_column = 0;
+ if (++terminal_row >= VGA_HEIGHT)
+ vscroll();
+ }
+ switch (c) {
+ default:
+ vsetchar(c, terminal_color, terminal_column, terminal_row);
+ terminal_column++;
+ break;
+ case '\n':
+ newline();
+ break;
+ case '\t':
+ // tab width of 4
+ terminal_column = (terminal_column / 4 + 1) * 4;
+ break;
+ case '\b':
+ if (terminal_column > 0) terminal_column--;
+ break;
+ case '\r':
+ terminal_column = 0;
+ break;
+ case '\f':
+ vclear();
+ break;
+ case '\a':
+ break;
+ }
+ if (terminal_column >= VGA_WIDTH)
+ newline();
+}
+
+void _putchar(char c) {
+ vputchar(c);
+}
+
+void vprint(const char* str) {
+ for (size_t i = 0; i < strlen(str); i++)
+ vputchar(str[i]);
+}
+
+void vlocate(size_t column, size_t row) {
+ terminal_column = column % VGA_WIDTH;
+ terminal_row = row % VGA_HEIGHT;
+}
+
diff --git a/src/vga.h b/src/vga.h
new file mode 100644
index 0000000..50cf1e6
--- /dev/null
+++ b/src/vga.h
@@ -0,0 +1,50 @@
+
+#ifndef __VGA_H
+#define __VGA_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+enum vga_color {
+ vga_black,
+ vga_blue,
+ vga_green,
+ vga_cyan,
+ vga_red,
+ vga_magenta,
+ vga_brown,
+ vga_lgray,
+ vga_dgray,
+ vga_lblue,
+ vga_lgreen,
+ vga_lcyan,
+ vga_lred,
+ vga_lmagenta,
+ vga_lbrown,
+ vga_white
+};
+
+uint8_t vga_color(enum vga_color fg, enum vga_color bg);
+uint16_t vga_entry(unsigned char uc, uint8_t color);
+
+static const size_t VGA_WIDTH = 80;
+static const size_t VGA_HEIGHT = 25;
+
+size_t terminal_row;
+size_t terminal_column;
+uint8_t terminal_color;
+uint16_t* terminal_buffer;
+
+void vclear(void);
+
+void vsetcolor(uint8_t color);
+
+void vsetchar(char c, uint8_t color, size_t x, size_t y);
+
+void vputchar(char c);
+
+void vprint(const char* str);
+
+void vlocate(size_t column, size_t row);
+
+#endif