├── .gitignore ├── README.org ├── build ├── build.el └── linker.ld ├── limine.cfg └── makefile /.gitignore: -------------------------------------------------------------------------------- 1 | limine/ 2 | *.o 3 | *.c 4 | *.h 5 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: ReadmeOS 2 | #+PROPERTY: header-args:c :tangle main.c 3 | This is a little kernel written in this repository's readme using literate programming with emacs org-mode. 4 | 5 | * Table of contents :TOC_4: 6 | - [[#building][Building]] 7 | - [[#prerequisites][Prerequisites]] 8 | - [[#building-and-running][Building and running]] 9 | - [[#code][Code]] 10 | - [[#includes][Includes]] 11 | - [[#stivale-tags][Stivale tags]] 12 | - [[#helper-functions][Helper functions]] 13 | - [[#drivers][Drivers]] 14 | - [[#com][COM]] 15 | - [[#printf-like-formatting][Printf-like formatting]] 16 | - [[#main-function][Main function]] 17 | 18 | * Building 19 | ** Prerequisites 20 | To build the kernel, you need: 21 | - Emacs with org-mode installed 22 | - x86_64-elf-gcc (if you don't have it, use gcc) 23 | - GNU Make 24 | - wget 25 | - git 26 | ** Building and running 27 | Run =make= and =make run= to build and run the kernel 28 | 29 | * Code 30 | ** Includes 31 | Includes the required files such as =stdint.h= for =uint=, =stddef.h= for =size_t=, =stdarg.h= for variadics arguments and =stivale2.h= for bootloader structs. 32 | #+BEGIN_SRC c 33 | #include 34 | #include 35 | #include 36 | #include 37 | #+END_SRC 38 | 39 | ** Stivale tags 40 | Sets up the proper tags to get from the stivale boot protocol; Here we want to get the terminal tag from stivale which will allow us to write to the screen easily and the framebuffer tag to initialize the framebuffer. 41 | #+BEGIN_SRC c 42 | static uint8_t stack[4096]; 43 | static struct stivale2_header_tag_terminal terminal_hdr_tag = { 44 | .tag = { 45 | .identifier = STIVALE2_HEADER_TAG_TERMINAL_ID, 46 | .next = 0 47 | }, 48 | .flags = 0 49 | }; 50 | 51 | static struct stivale2_header_tag_framebuffer framebuffer_hdr_tag = { 52 | .tag = { 53 | .identifier = STIVALE2_HEADER_TAG_FRAMEBUFFER_ID, 54 | 55 | .next = (uint64_t)&terminal_hdr_tag 56 | }, 57 | 58 | .framebuffer_width = 0, 59 | .framebuffer_height = 0, 60 | .framebuffer_bpp = 0 61 | }; 62 | 63 | __attribute__((section(".stivale2hdr"), used)) 64 | static struct stivale2_header stivale_hdr = { 65 | 66 | .entry_point = 0, 67 | 68 | .stack = (uintptr_t)stack + sizeof(stack), 69 | 70 | .flags = (1 << 1) | (1 << 2), 71 | 72 | .tags = (uintptr_t)&framebuffer_hdr_tag 73 | }; 74 | 75 | #+END_SRC 76 | The following function will allow us to scan for tags that we want 77 | 78 | #+BEGIN_SRC c 79 | void *stivale2_get_tag(struct stivale2_struct *stivale2_struct, uint64_t id) 80 | { 81 | struct stivale2_tag *current_tag = (void *)stivale2_struct->tags; 82 | for (;;) 83 | { 84 | if (!current_tag) 85 | { 86 | return NULL; 87 | } 88 | 89 | if (current_tag->identifier == id) 90 | { 91 | return current_tag; 92 | } 93 | 94 | current_tag = (void *)current_tag->next; 95 | } 96 | } 97 | #+END_SRC 98 | 99 | ** Helper functions 100 | This is where we'll write helper functions for our kernel, let's start by assembly ones. 101 | 102 | #+BEGIN_SRC c 103 | uint8_t inb(uint16_t port) 104 | { 105 | uint8_t data; 106 | __asm__ volatile("inb %1, %0" 107 | : "=a"(data) 108 | : "d"(port)); 109 | return data; 110 | } 111 | 112 | void outb(uint16_t port, uint8_t data) 113 | { 114 | __asm__ volatile("outb %0, %1" 115 | : 116 | : "a"(data), "Nd"(port)); 117 | } 118 | #+END_SRC 119 | 120 | Then string ones 121 | 122 | #+BEGIN_SRC c 123 | size_t strlen(char *str) 124 | { 125 | size_t i; 126 | for (i = 0; str[i] != '\0'; i++); 127 | return i; 128 | } 129 | 130 | char *strncat(char *dest, char *src, size_t n) 131 | { 132 | size_t dest_length = strlen(dest); 133 | size_t i; 134 | 135 | for (i = 0; i < n && src[i] != '\0'; i++) 136 | { 137 | dest[dest_length + i] = src[i]; 138 | } 139 | 140 | dest[dest_length + i] = '\0'; 141 | 142 | return dest; 143 | } 144 | 145 | char *strcat(char *dest, char *src) 146 | { 147 | return strncat(dest, src, strlen(src)); 148 | } 149 | 150 | // This function isn't perfect but it works for now :) 151 | char *string_convert(unsigned int num, int base) 152 | { 153 | static char Representation[] = "0123456789ABCDEF"; 154 | static char buffer[50]; 155 | char *ptr; 156 | 157 | ptr = &buffer[49]; 158 | ,*ptr = '\0'; 159 | 160 | do 161 | { 162 | ,*--ptr = Representation[num % base]; 163 | num /= base; 164 | } while (num != 0); 165 | return (ptr); 166 | } 167 | 168 | void vsprintf(char *str, char *format, va_list arg) 169 | { 170 | unsigned int i; 171 | unsigned int ZERO = 0; 172 | char *s; 173 | 174 | int position = 0; 175 | 176 | while (*format) 177 | { 178 | 179 | if (*format == '%') 180 | { 181 | format++; 182 | switch (*format) 183 | { 184 | case 'c': 185 | i = va_arg(arg, int); 186 | str[position] = i; 187 | position++; 188 | break; 189 | 190 | case 'd': 191 | i = va_arg(arg, int); 192 | if (i < ZERO) 193 | { 194 | i = -i; 195 | str[position] = '-'; 196 | } 197 | strcat(str, string_convert(i, 10)); 198 | position += strlen(string_convert(i, 10)); 199 | 200 | break; 201 | 202 | case 'o': 203 | i = va_arg(arg, unsigned int); 204 | strcat(str, string_convert(i, 8)); 205 | position += strlen(string_convert(i, 8)); 206 | break; 207 | 208 | case 's': 209 | s = va_arg(arg, char *); 210 | strcat(str, s); 211 | position += strlen(s); 212 | break; 213 | 214 | case 'x': 215 | i = va_arg(arg, unsigned int); 216 | strcat(str, string_convert(i, 16)); 217 | position += strlen(string_convert(i, 16)); 218 | break; 219 | 220 | default: 221 | str[position] = '%'; 222 | position++; 223 | break; 224 | } 225 | } 226 | 227 | else 228 | { 229 | str[position] = *format; 230 | position++; 231 | } 232 | 233 | format++; 234 | } 235 | } 236 | #+END_SRC 237 | 238 | ** Drivers 239 | *** COM 240 | In this part of the kernel, we'll setup a COM driver to debug our kernel. 241 | #+BEGIN_SRC c 242 | typedef enum 243 | { 244 | COM1 = 0x3F8, 245 | COM2 = 0x2F8, 246 | COM3 = 0x3E8, 247 | COM4 = 0x2E8 248 | } SerialPort; 249 | 250 | static int is_transmit_empty(SerialPort port) 251 | { 252 | return inb(port + 5) & 0x20; 253 | } 254 | 255 | static int serial_received(SerialPort port) 256 | { 257 | return inb(port + 5) & 1; 258 | } 259 | 260 | void com_initialize(SerialPort port) 261 | { 262 | outb(port + 1, 0x00); 263 | outb(port + 3, 0x80); 264 | outb(port + 0, 0x03); 265 | outb(port + 1, 0x00); 266 | outb(port + 3, 0x03); 267 | outb(port + 2, 0xC7); 268 | outb(port + 4, 0x0B); 269 | } 270 | 271 | void com_putc(SerialPort port, char c) 272 | { 273 | while (is_transmit_empty(port) == 0); 274 | outb(port, c); 275 | } 276 | 277 | void com_write_string(SerialPort port, char *str) 278 | { 279 | while (*str) 280 | { 281 | com_putc(port, *str++); 282 | } 283 | } 284 | 285 | 286 | char com_getc(SerialPort port) 287 | { 288 | while (serial_received(port) == 0); 289 | return inb(port); 290 | } 291 | #+END_SRC 292 | 293 | **** Printf-like formatting 294 | To extend our COM driver, we can add formatting to it using printf-like formatting, let's do that!! 295 | 296 | #+BEGIN_SRC c 297 | void printf(char *format, ...) 298 | { 299 | 300 | va_list arg; 301 | va_start(arg, format); 302 | 303 | 304 | char message[4096] = {0}; 305 | vsprintf(message, format, arg); 306 | com_write_string(COM1, message); 307 | va_end(arg); 308 | } 309 | #+END_SRC 310 | 311 | ** Main function 312 | This is our kernel's entry point 313 | #+BEGIN_SRC c 314 | void _start(struct stivale2_struct *stivale2_struct) { 315 | struct stivale2_struct_tag_terminal *term_str_tag; 316 | term_str_tag = stivale2_get_tag(stivale2_struct, STIVALE2_STRUCT_TAG_TERMINAL_ID); 317 | 318 | if (!term_str_tag) { 319 | for (;;) { 320 | __asm__ volatile("hlt"); 321 | } 322 | } 323 | 324 | void *term_write_ptr = (void *)term_str_tag->term_write; 325 | 326 | void (*term_write)(const char *string, size_t length) = term_write_ptr; 327 | 328 | term_write("Welcome to org-kernel", 21); 329 | com_initialize(COM1); 330 | 331 | for (;;) { 332 | __asm__("hlt"); 333 | } 334 | } 335 | #+END_SRC 336 | -------------------------------------------------------------------------------- /build/build.el: -------------------------------------------------------------------------------- 1 | (require 'seq) 2 | (require 'org) 3 | (require 'ob-tangle) 4 | (require 'ox-org) 5 | 6 | (org-babel-tangle-file (elt argv 0)) 7 | -------------------------------------------------------------------------------- /build/linker.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want the symbol _start to be our entry point */ 2 | ENTRY(_start) 3 | 4 | /* Define the program headers we want so the bootloader gives us the right */ 5 | /* MMU permissions */ 6 | PHDRS 7 | { 8 | null PT_NULL FLAGS(0) ; /* Null segment */ 9 | rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ 10 | text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ 11 | data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ 12 | dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ; /* Dynamic segment needed for PIE */ 13 | } 14 | 15 | SECTIONS 16 | { 17 | /* We wanna be placed in the higher half, 2MiB above 0 in physical memory. */ 18 | /* Since we are going to use PIE, this is just the base load address, but the */ 19 | /* bootloader will be able to relocate us as it sees fit. */ 20 | . = 0xffffffff80200000; 21 | 22 | /* We place the .stivale2hdr section containing the header in its own section, */ 23 | /* and we use the KEEP directive on it to make sure it doesn't get discarded. */ 24 | .stivale2hdr ALIGN(4K) : { 25 | KEEP(*(.stivale2hdr)) 26 | } :rodata 27 | 28 | /* Then let's place all the other traditional executable sections afterwards. */ 29 | .rodata ALIGN(4K) : { 30 | *(.rodata*) 31 | } :rodata 32 | 33 | .text ALIGN(4K) : { 34 | *(.text*) 35 | } :text 36 | 37 | .data ALIGN(4K) : { 38 | *(.data*) 39 | } :data 40 | 41 | /* Dynamic section needed for PIE */ 42 | .dynamic ALIGN(4K) : { 43 | *(.dynamic) 44 | } :data :dynamic 45 | 46 | .bss ALIGN(4K) : { 47 | *(COMMON) 48 | *(.bss*) 49 | } :data 50 | } 51 | -------------------------------------------------------------------------------- /limine.cfg: -------------------------------------------------------------------------------- 1 | # Timeout in seconds that Limine will use before automatically booting. 2 | TIMEOUT=3 3 | 4 | :stivale2-barebones 5 | KASLR=no 6 | PROTOCOL=stivale2 7 | KERNEL_PATH=boot:///kernel.elf 8 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | KERNEL := kernel.elf 2 | 3 | CC = x86_64-elf-gcc 4 | LD = x86_64-elf-ld 5 | 6 | CFLAGS = -Wall -Wextra -O2 -pipe 7 | LDFLAGS = 8 | 9 | INTERNALLDFLAGS := \ 10 | -Tbuild/linker.ld \ 11 | -nostdlib \ 12 | -zmax-page-size=0x1000 \ 13 | -static \ 14 | -pie \ 15 | --no-dynamic-linker \ 16 | -ztext 17 | 18 | INTERNALCFLAGS := \ 19 | -Iinclude/ \ 20 | -std=c11 \ 21 | -ffreestanding \ 22 | -fno-stack-protector \ 23 | -fno-pic -fpie \ 24 | -mno-80387 \ 25 | -mno-mmx \ 26 | -mno-3dnow \ 27 | -mno-sse \ 28 | -mno-sse2 \ 29 | -mno-red-zone 30 | 31 | ORGFILES := README.org 32 | CFILES := main.c 33 | OBJ := main.o 34 | 35 | ISO_IMAGE = disk.iso 36 | 37 | .PHONY: clean all run 38 | 39 | all: $(ISO_IMAGE) 40 | 41 | run: $(ISO_IMAGE) 42 | qemu-system-x86_64 -M q35 -m 2G -cdrom $(ISO_IMAGE) -enable-kvm -serial stdio 43 | 44 | limine: 45 | git clone https://github.com/limine-bootloader/limine.git --branch=v2.0-branch-binary --depth=1 46 | make -C limine 47 | 48 | kernel.elf: $(OBJ) 49 | $(LD) $(OBJ) $(LDFLAGS) $(INTERNALLDFLAGS) -o $@ 50 | 51 | main.c: 52 | emacs --script build/build.el README.org 53 | 54 | .PHONY: all clean 55 | 56 | all: $(KERNEL) 57 | 58 | include/stivale2.h: 59 | cd include && wget https://github.com/stivale/stivale/raw/master/stivale2.h && cd .. 60 | 61 | main.o: $(CFILES) include/stivale2.h 62 | $(CC) $(CFLAGS) $(INTERNALCFLAGS) -c $< -o $@ 63 | 64 | $(ISO_IMAGE): limine $(KERNEL) 65 | rm -rf iso_root 66 | mkdir -p iso_root 67 | @cp kernel.elf \ 68 | limine.cfg limine/limine.sys limine/limine-cd.bin limine/limine-eltorito-efi.bin iso_root/ > /dev/null 2>&1 69 | @xorriso -as mkisofs -b limine-cd.bin \ 70 | -no-emul-boot -boot-load-size 4 -boot-info-table \ 71 | --efi-boot limine-eltorito-efi.bin \ 72 | -efi-boot-part --efi-boot-image --protective-msdos-label \ 73 | iso_root -o $(ISO_IMAGE) > /dev/null 2>&1 74 | limine/limine-install $(ISO_IMAGE) 75 | rm -rf iso_root 76 | 77 | clean: 78 | rm -f $(ISO_IMAGE) 79 | rm -rf $(KERNEL) $(OBJ) $(CFILES) 80 | --------------------------------------------------------------------------------