/readme-os

A little kernel written in the README file of the repo

Primary LanguageMakefile

ReadmeOS

This is a little kernel written in this repository’s readme using literate programming with emacs org-mode.

Table of contents

Building

Prerequisites

To build the kernel, you need:

  • Emacs with org-mode installed
  • x86_64-elf-gcc (if you don’t have it, use gcc)
  • GNU Make
  • wget
  • git

Building and running

Run make and make run to build and run the kernel

Code

Includes

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.

#include <stdint.h>
#include <stddef.h>
#include <stivale2.h>
#include <stdarg.h>

Stivale tags

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.

static uint8_t stack[4096];
static struct stivale2_header_tag_terminal terminal_hdr_tag = {
    .tag = {
        .identifier = STIVALE2_HEADER_TAG_TERMINAL_ID,
        .next = 0
    },
    .flags = 0
};

static struct stivale2_header_tag_framebuffer framebuffer_hdr_tag = {
    .tag = {
        .identifier = STIVALE2_HEADER_TAG_FRAMEBUFFER_ID,

        .next = (uint64_t)&terminal_hdr_tag
    },

    .framebuffer_width  = 0,
    .framebuffer_height = 0,
    .framebuffer_bpp    = 0
};

__attribute__((section(".stivale2hdr"), used))
static struct stivale2_header stivale_hdr = {

    .entry_point = 0,

    .stack = (uintptr_t)stack + sizeof(stack),
 
    .flags = (1 << 1) | (1 << 2),

    .tags = (uintptr_t)&framebuffer_hdr_tag
};

The following function will allow us to scan for tags that we want

void *stivale2_get_tag(struct stivale2_struct *stivale2_struct, uint64_t id)
{
              struct stivale2_tag *current_tag = (void *)stivale2_struct->tags;
              for (;;)
              {
                  if (!current_tag)
                  {
                      return NULL;
                  }
  
                  if (current_tag->identifier == id)
                  {
                      return current_tag;
                  }
  
                  current_tag = (void *)current_tag->next;
              }
}

Helper functions

This is where we’ll write helper functions for our kernel, let’s start by assembly ones.

uint8_t inb(uint16_t port)
{
    uint8_t data;
    __asm__ volatile("inb %1, %0"
                     : "=a"(data)
                     : "d"(port));
    return data;
}

void outb(uint16_t port, uint8_t data)
{
    __asm__ volatile("outb %0, %1"
                     :
                     : "a"(data), "Nd"(port));
}

Then string ones

    size_t strlen(char *str)
    {
        size_t i;
        for (i = 0; str[i] != '\0'; i++);
        return i;
    }

    char *strncat(char *dest, char *src, size_t n)
    {
        size_t dest_length = strlen(dest);
        size_t i;

        for (i = 0; i < n && src[i] != '\0'; i++)
        {
            dest[dest_length + i] = src[i];
        }

        dest[dest_length + i] = '\0';

        return dest;
    }

    char *strcat(char *dest, char *src)
    {
        return strncat(dest, src, strlen(src));
    }

  // This function isn't perfect but it works for now :)
  char *string_convert(unsigned int num, int base)
  {
      static char Representation[] = "0123456789ABCDEF";
      static char buffer[50];
      char *ptr;

      ptr = &buffer[49];
      *ptr = '\0';

      do
      {
          *--ptr = Representation[num % base];
          num /= base;
      } while (num != 0);
      return (ptr);
  }

void vsprintf(char *str, char *format, va_list arg)
{
    unsigned int i;
    unsigned int ZERO = 0;
    char *s;

    int position = 0;

    while (*format)
    {

        if (*format == '%')
        {
            format++;
            switch (*format)
            {
            case 'c':
                i = va_arg(arg, int);
                str[position] = i;
                position++;
                break;

            case 'd':
                i = va_arg(arg, int);
                if (i < ZERO)
                {
                    i = -i;
                    str[position] = '-';
                }
                strcat(str, string_convert(i, 10));
                position += strlen(string_convert(i, 10));

                break;

            case 'o':
                i = va_arg(arg, unsigned int);
                strcat(str, string_convert(i, 8));
                position += strlen(string_convert(i, 8));
                break;

            case 's':
                s = va_arg(arg, char *);
                strcat(str, s);
                position += strlen(s);
                break;

            case 'x':
                i = va_arg(arg, unsigned int);
                strcat(str, string_convert(i, 16));
                position += strlen(string_convert(i, 16));
                break;

            default:
                str[position] = '%';
                position++;
                break;
            }
        }

        else
        {
            str[position] = *format;
            position++;
        }

        format++;
    }
}

Drivers

COM

In this part of the kernel, we’ll setup a COM driver to debug our kernel.

typedef enum
{
      COM1 = 0x3F8,
      COM2 = 0x2F8,
      COM3 = 0x3E8,
      COM4 = 0x2E8
} SerialPort;

static int is_transmit_empty(SerialPort port)
{
   return inb(port + 5) & 0x20;
}

static int serial_received(SerialPort port)
{
   return inb(port + 5) & 1;
}

void com_initialize(SerialPort port)
{
   outb(port + 1, 0x00);
   outb(port + 3, 0x80);
   outb(port + 0, 0x03);
   outb(port + 1, 0x00);
   outb(port + 3, 0x03);
   outb(port + 2, 0xC7);
   outb(port + 4, 0x0B);
 }

void com_putc(SerialPort port, char c)
{
    while (is_transmit_empty(port) == 0);
    outb(port, c);
}

void com_write_string(SerialPort port, char *str)
{
  while (*str)
  {
      com_putc(port, *str++);
  }
}


char com_getc(SerialPort port)
{
    while (serial_received(port) == 0);
    return inb(port);
}

Printf-like formatting

To extend our COM driver, we can add formatting to it using printf-like formatting, let’s do that!!

void printf(char *format, ...)
{

    va_list arg;
    va_start(arg, format);


    char message[4096] = {0};
    vsprintf(message, format, arg);
    com_write_string(COM1, message);
    va_end(arg);
}

Main function

This is our kernel’s entry point

 void _start(struct stivale2_struct *stivale2_struct) {
    struct stivale2_struct_tag_terminal *term_str_tag;
    term_str_tag = stivale2_get_tag(stivale2_struct, STIVALE2_STRUCT_TAG_TERMINAL_ID);

    if (!term_str_tag) {
        for (;;) {
            __asm__ volatile("hlt");
        }
    }

    void *term_write_ptr = (void *)term_str_tag->term_write;

    void (*term_write)(const char *string, size_t length) = term_write_ptr;

    term_write("Welcome to org-kernel", 21);
    com_initialize(COM1);

    for (;;) {
        __asm__("hlt");
    }
}