From e03bf1f1ec1230117ca358a75624f2c50c01f045 Mon Sep 17 00:00:00 2001 From: the lemons Date: Wed, 31 Mar 2021 00:35:03 -0500 Subject: initial files --- Makefile | 54 +++ asm_obj/boot.o | Bin 0 -> 18332 bytes asm_obj/interrupt_stub.o | Bin 0 -> 1620 bytes grub.cfg | 6 + isodir/boot/grub/grub.cfg | 6 + isodir/boot/kernel.bin | Bin 0 -> 86632 bytes kernel.bin | Bin 0 -> 86632 bytes linker.ld | 26 ++ obj/bees.o | Bin 0 -> 4076 bytes obj/interrupt.o | Bin 0 -> 8668 bytes obj/keyboard.o | Bin 0 -> 6180 bytes obj/main.o | Bin 0 -> 6720 bytes obj/port.o | Bin 0 -> 3168 bytes obj/printf.o | Bin 0 -> 32208 bytes obj/string.o | Bin 0 -> 5652 bytes obj/vga.o | Bin 0 -> 7656 bytes os.iso | Bin 0 -> 19890176 bytes src/.tags | 119 ++++++ src/bees.c | 22 ++ src/bees.h | 6 + src/boot.s | 80 ++++ src/interrupt.c | 92 +++++ src/interrupt.h | 46 +++ src/interrupt_stub.s | 54 +++ src/keyboard.c | 98 +++++ src/keyboard.h | 15 + src/main.c | 45 +++ src/port.c | 38 ++ src/port.h | 19 + src/printf.c | 914 ++++++++++++++++++++++++++++++++++++++++++++++ src/printf.h | 117 ++++++ src/string.c | 64 ++++ src/string.h | 15 + src/vga.c | 103 ++++++ src/vga.h | 50 +++ 35 files changed, 1989 insertions(+) create mode 100644 Makefile create mode 100644 asm_obj/boot.o create mode 100644 asm_obj/interrupt_stub.o create mode 100644 grub.cfg create mode 100644 isodir/boot/grub/grub.cfg create mode 100755 isodir/boot/kernel.bin create mode 100755 kernel.bin create mode 100644 linker.ld create mode 100644 obj/bees.o create mode 100644 obj/interrupt.o create mode 100644 obj/keyboard.o create mode 100644 obj/main.o create mode 100644 obj/port.o create mode 100644 obj/printf.o create mode 100644 obj/string.o create mode 100644 obj/vga.o create mode 100644 os.iso create mode 100644 src/.tags create mode 100644 src/bees.c create mode 100644 src/bees.h create mode 100644 src/boot.s create mode 100644 src/interrupt.c create mode 100644 src/interrupt.h create mode 100644 src/interrupt_stub.s create mode 100644 src/keyboard.c create mode 100644 src/keyboard.h create mode 100644 src/main.c create mode 100644 src/port.c create mode 100644 src/port.h create mode 100644 src/printf.c create mode 100644 src/printf.h create mode 100644 src/string.c create mode 100644 src/string.h create mode 100644 src/vga.c create mode 100644 src/vga.h 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 Binary files /dev/null and b/asm_obj/boot.o differ diff --git a/asm_obj/interrupt_stub.o b/asm_obj/interrupt_stub.o new file mode 100644 index 0000000..669b218 Binary files /dev/null and b/asm_obj/interrupt_stub.o 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 Binary files /dev/null and b/isodir/boot/kernel.bin differ diff --git a/kernel.bin b/kernel.bin new file mode 100755 index 0000000..c0362ec Binary files /dev/null and b/kernel.bin 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 Binary files /dev/null and b/obj/bees.o differ diff --git a/obj/interrupt.o b/obj/interrupt.o new file mode 100644 index 0000000..62fa47b Binary files /dev/null and b/obj/interrupt.o differ diff --git a/obj/keyboard.o b/obj/keyboard.o new file mode 100644 index 0000000..f8457ab Binary files /dev/null and b/obj/keyboard.o differ diff --git a/obj/main.o b/obj/main.o new file mode 100644 index 0000000..4e314ac Binary files /dev/null and b/obj/main.o differ diff --git a/obj/port.o b/obj/port.o new file mode 100644 index 0000000..3643494 Binary files /dev/null and b/obj/port.o differ diff --git a/obj/printf.o b/obj/printf.o new file mode 100644 index 0000000..fea2cec Binary files /dev/null and b/obj/printf.o differ diff --git a/obj/string.o b/obj/string.o new file mode 100644 index 0000000..4afe28a Binary files /dev/null and b/obj/string.o differ diff --git a/obj/vga.o b/obj/vga.o new file mode 100644 index 0000000..44460d2 Binary files /dev/null and b/obj/vga.o differ diff --git a/os.iso b/os.iso new file mode 100644 index 0000000..db860d2 Binary files /dev/null and b/os.iso 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 +#include +#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 +#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 + +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 +#include + +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 +#include + +#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 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 +#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 +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 +#include + + +#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 +#include + + +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> (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 +#include + +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 +#include + +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 +#include + +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 -- cgit v1.2.3