See also: Home, Build Guide, Roadmap

Contributing

Thank you for your interest in contributing to Valecium OS. Please read this guide fully before opening a pull request or submitting a patch.

Setting Up and Building

Building is covered in detail in Building Valecium OS. The build system uses SCons. Key files are:

  • SConstruct — Top-level build configuration (version 0.29)

  • kernel/SConscript — Kernel build configuration

  • scripts/scons/arch.py — Architecture detection (i686, x86_64, aarch64)

  • scripts/base/toolchain.py — Cross-compiler builder (binutils 2.45 + musl 1.2.6 + GCC 15.2.0)

Common build commands:

scons                                        # Build everything (debug)
scons BuildConfig=release                    # Release build
scons BuildArch=i686 BuildConfig=debug       # Specific architecture
scons BuildType=kernel                       # Kernel only
scons BuildType=bootloader                   # Bootloader only

Testing and Debugging

GDB

scons debug

This launches QEMU with -s -S (waits for GDB on TCP port 1234) and opens GDB with kernel symbols pre-loaded.

Self-Tests

Several subsystems run self-tests on initialisation:

void Crypto_SelfTest(void);    // MD5 and SHA1 verification
void VFS_SelfTest(void);       // Virtual filesystem operations
void Process_SelfTest(void);   // Process creation and scheduling
void Heap_SelfTest(void);      // Kernel heap allocator

Git Workflow

This project uses Git. Requirements for contributions:

Commits: The message must describe the change meaningfully. Placeholders like update are not acceptable.

Pull requests: Always branch off a feature branch, never your fork’s master. Keep one PR per logical change. Describe what changed and why.

Patches: Send via email, generated from committed changes with git send-email (not a raw diff). Include a brief explanation in the email body.

Coding Style

Licensing Headers

Every source file must begin with an SPDX identifier:

// SPDX-License-Identifier: GPL-3.0-only   // kernel C/assembly files
# SPDX-License-Identifier: BSD-3-Clause    // build scripts
// SPDX-License-Identifier: CC-BY-SA-4.0   // documentation

Indentation and Formatting

  • 3 spaces per indent level. No tabs.

  • Maximum line length: 80 characters (enforced by the formatter).

  • Spaces around binary operators: a + b, x = y.

  • Space after control-flow keywords: if (x), for (;;), while (c).

Naming Conventions

Functions

Global (non-static) functions use PascalCase with a subsystem prefix:

void CPU_Initialize(void);
void Keyboard_HandleScancode(uint8_t scancode);

Static (file-local) functions use snake_case:

static void keyboard_buffer_push(char c);
static inline void mark_dirty(TTY_Device *tty, int row);

Variables

Variable naming depends on scope — this distinction is mandatory:

Scope Convention Example

Global (extern or file-scope non-static)

g_PascalCase

g_KeyboardBuffer, g_HalVideoOperations

File-scope static

s_PascalCase

s_BootDrive, s_RootDirLBA

Local (function-scope)

snake_case

page_dir, fault_address

Struct members — values and nested structs

snake_case

limit_low, base_addr, ref_count

Struct members — function pointers

Subsystem_Name or Name (if parent struct is subsystem)

Stack_GetEBP, PutChar

Function pointer members follow the global function convention because they behave as callable operations. A struct mixing both kinds looks like:

typedef struct
{
   // value and nested-struct members: snake_case
   uint32_t base_addr;
   uint32_t size;
   FS_File  root_file;

   // function pointer members that have the structure as their parent subsystem: Name
   int (*Open)(const char *path, int flags);
   void (*Close)(int fd);

   // function pointers that does not relate to the subsystem the structure is at: Subsystem_Name
   int (*INODE_GetName)(int inode);
} FS_Operations;

Macros and Constants

All macros and compile-time constants use UPPER_SNAKE_CASE with at most three underscore-separated words excluding suffixes:

#define PAGE_SIZE       4096
#define MAX_OPEN_FILES  8
#define SECTOR_SIZE_ISO 2048

#define PAGE_ALIGN_UP(v)   (((v) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))
#define PAGE_ALIGN_DOWN(v) ((v) & ~(PAGE_SIZE - 1))

Types and Structs

Struct typedefs use PascalCase prefixed by the subsystem if that structure is global:

typedef struct { ... } FS_File;
typedef struct { ... } MBR_PartitionEntry;
typedef struct { ... } GDTEntry;

Private structures defined in C files should only have the Name in PascalCase with no subsystem declaration:

typedef struct { ... } CoreFsOperations;
typedef struct { ... } MbiTagFramebuffer;

Enums follow the same pattern, with members prefixed by the enum name:

typedef enum
{
   GDT_ACCESS_CODE_READABLE = 0x02,
   GDT_ACCESS_RING0         = 0x00,
   GDT_ACCESS_PRESENT       = 0x80,
} GDT_ACCESS;

Comments

Keep comments short and purposeful. A comment should explain why something is done, not restate what the code already says. Avoid large decorative comment blocks.

Single-line C comments use //:

// Initialize the keyboard subsystem
void Keyboard_Initialize(void);

Multi-line documentation should be short and simple, keep it at 1 line after the function prototype.

void Keyboard_HandleScancode(uint8_t scancode); // Generic keyboard handler

Inline clarifications use /* */ sparingly:

/* Cap at 32-bit max to prevent wrap-around */
if (desired_end > 0xFFFFFFFFu)
   heap_end = 0xFFFFFFFFu;

Assembly (.S files) uses #:

pushl %ebp        # save old call frame
movl %esp, %ebp   # initialize new call frame

Avoid:

  • Multi-line banners or box-drawing comments

  • Redundant comments that repeat the function name or variable type

  • Comments that become stale and contradict the code

Brace Style

Opening braces go on the next line (Allman style):

void CPU_Initialize(void)
{
   Process_SelfTest();
}

if (x < y)
{
   do_something();
}
else
{
   do_other();
}

Source File Layout

Files must follow this top-down order:

  1. License identifier and #include directives

  2. Forward declarations (struct tags and static function prototypes)

  3. Macros (#define constants, at most 3 underscore-separated words per name)

  4. Struct and typedef definitions

  5. Global and static variable definitions

  6. External declarations

  7. Private (static) helper functions

  8. Public API functions

// 1. License & includes
// SPDX-License-Identifier: GPL-3.0-only
#include <stdbool.h>
#include <stdint.h>
#include <cpu/cpu.h>

// 2. Forward declarations
struct FS_File;
static int resolve_path(const char *path, uint32_t *out_lba);

// 3. Macros
#define MAX_OPEN_FILES 8

// 4. Structs
typedef struct { int used; uint64_t start_lba; uint32_t size; uint32_t pos; } FS_File;

// 5. Variables
static uint32_t s_BootDrive = 0;

// 6. External declarations
extern int DISK_ReadLBA(uint8_t drive, uint64_t lba, uint16_t count, void *buf);

// 7. Private functions
static int resolve_path(const char *path, uint32_t *out_lba) { /* ... */ }

// 8. Public functions
int FS_Open(const char *path) { /* ... */ }

Include Order

System headers first, then project headers, separated by a blank line. Within each group, headers are sorted alphabetically (enforced by the formatter):

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include <cpu/cpu.h>
#include <mem/mm_kernel.h>
#include <std/stdio.h>

Inline Functions

Use static inline for simple, frequently-called operations:

static inline void invalidate_tlb_entry(uint32_t vaddr)
{
   __asm__ volatile("invlpg (%0)" ::"r"(vaddr) : "memory");
}

static inline FAT_Instance *fat_inst(const Partition *disk)
{
   return (FAT_Instance *)disk->fs_instance;
}

Pointer Style

Pointer declarators attach to the variable name:

void *ptr;
char *str;
int  *array;

Always check pointers before dereferencing:

if (!page_dir)
{
   logfmt(LOG_ERROR, "[MEM] no kernel page directory!\n");
   return;
}

Error Handling

Follow Unix conventions: return 0 on success, -ERRNO on failure (list of errnos can be found at /include/constants.h). Use NULL for pointer-returning functions that fail:

int Mem_Brk(void *addr);      // 0 on success, -EINVAL on failure
void *Mem_Sbrk(intptr_t inc); // previous break, or NULL on failure

int SYS_Open(const char *path, int flags)
{
   if (!path) return -EINVAL;
   // ...
}

void *DL_Open(const char *path)
{
   if (!path) return NULL;
   // ...
}

Log errors and events with logfmt() in kernel:

logfmt(LOG_INFO,  "[MEM] start=0x%08x end=0x%08x size=%u MB\n",
       (uint32_t)heap_start, (uint32_t)heap_end, size_mb);

logfmt(LOG_ERROR, "[VFS] Mount point '%s' must start with '/'", location);

Header Guards

Prefer #pragma once:

#pragma once

#define min(a, b) ((a) < (b) ? (a) : (b))

Traditional guards are acceptable for compatibility:

#ifndef SCHEDULER_H
#define SCHEDULER_H
// ...
#endif

Low-Level Structs and Macros

Use attributepacked for hardware-mapped structures:

typedef struct
{
   uint16_t limit_low;
   uint16_t base_low;
   uint8_t  base_mid;
   uint8_t  access;
   uint8_t  flags_limit_hi;
   uint8_t  base_high;
} __attribute__((packed)) GDT_Entry;

Parametric macros for structure construction:

#define GDT_ENTRY(base, limit, access, flags) \
   {                                           \
      GDT_LIMIT_LOW(limit),                   \
      GDT_BASE_LOW(base),                     \
      GDT_BASE_MID(base),                     \
      access,                                  \
      GDT_FLAGS_LIMIT_HI(limit, flags),       \
      GDT_BASE_HIGH(base)                     \
   }

Hardware Abstraction Layer (HAL)

Valecium targets multiple architectures. Never put x86-specific assembly in platform-independent code. All architecture-specific operations go through the HAL.

Using HAL Operations

// Correct: platform-independent
uintptr_t ebp = g_HalStackOperations->Stack_GetEBP();
g_HalPagingOperations->Paging_FlushTlb();
g_HalIoOperations->IO_Panic();

// Wrong: never call arch functions directly from kernel code
// i686_Stack_GetEBP();

Architecture Directory Layout

Architecture implementations live under kernel/arch/<arch>/:

  • boot/ — Entry point, multiboot parsing

  • cpu/ — GDT, IDT, ISR, IRQ, TSS, PIT, PIC, scheduler, usermode

  • drivers/ — PS/2, serial

  • io/ — Port I/O and interrupt control

  • mem/ — Paging, TLB, stack, memory layout

  • signal/ — Signal frame, sigreturn

  • syscall/int 0x80 handler

  • video/ — VGA text mode

Assembly File Rules

Assembly implementations must be placed in .S files (GNU assembler with C preprocessor) inside the appropriate kernel/arch/<arch>/ subdirectory, exported via .global:

# kernel/arch/i686/cpu/gdt_asm.S
# SPDX-License-Identifier: GPL-3.0-only

.code32

# void i686_GDT_Load(GDTDescriptor *descriptor, ...)
.global i686_GDT_Load
i686_GDT_Load:
   pushl %ebp
   movl %esp, %ebp
   movl 8(%ebp), %eax
   lgdt (%eax)
   popl %ebp
   ret

HAL Mapping Pattern

Architecture functions are mapped in headers with #if defined(ARCH):

// kernel/hal/stack.h
#if defined(I686)
#include <arch/i686/cpu/stack.h>
#define HAL_ARCH_Stack_GetEBP i686_Stack_GetEBP
#define HAL_ARCH_Stack_GetESP i686_Stack_GetESP
#else
#error "Unsupported architecture for HAL Stack"
#endif

Then wired up in hal.c:

const HAL_StackOperations *g_HalStackOperations = &(HAL_StackOperations){
   .Stack_GetEBP = HAL_ARCH_Stack_GetEBP,
   .Stack_GetESP = HAL_ARCH_Stack_GetESP,
};

Kernel code only ever touches g_Hal* — never arch-specific names.

Subsystems

Crypto (kernel/crypto/)

MD5 and SHA1 implementations, each with Init, Update, Final, Calculate, ToHex, and SelfTest functions. Self-tests run during kernel initialisation to verify correctness.

Kernel Module System (kernel/sys/kmod/)

Supports dynamic loading of kernel modules (kmod):

  • Loading from disk or memory image

  • Symbol resolution via a global symbol table

  • Dependency tracking

  • PLT/GOT relocation patching

  • Up to 16 modules, 256 symbols per module, 1024 global symbols

Virtual Memory Layout (i686)

Defined in kernel/arch/i686/mem/vm_layout.h:

Region Address

KERNEL_BASE

0xC0000000 (3 GB)

USER_SPACE_END

0xC0000000

USER_CODE_START

0x08048000

USER_HEAP_START

0x10000000 (256 MB)

USER_STACK_START

0xBFFF0000

USER_STACK_SIZE

0x10000 (64 KB)

Documentation Standards

All documentation uses AsciiDoc (.ad). Files must include:

= Document Title
:toc: left
:icons: font
:sectlinks:

Ensure compatibility with pandoc conversion to PDF, HTML, and man pages.

Revision History

Every documentation file must include a Revision History section at the end. Use semantic versioning: major for significant reorganisation, minor for clarifications or new sections, patch for typo fixes (batch into next minor).

== Revision History

=== v1.0
Initial documentation.

=== v1.1
Clarified X, added examples for Y.

Revision History

v3.2

Added struct member naming exception: function pointer members use Subsystem_Name (same as global functions); plain value and nested-struct members keep snake_case. Added illustrative mixed-member struct example. Updated HAL examples accordingly.

v3.1

Fixed all code examples to comply with stated conventions: struct members corrected to snake_case (GDTEntry fields, HAL function pointer members); global functions corrected to PascalCase subsystem prefix (Mem_Brk, Mem_Sbrk, Sys_Open, Dl_Open); assembly comment style corrected to #; column limit updated to 80; include examples sorted alphabetically per SortIncludes: true; HAL macro names updated to match corrected member names.

v3.0

Complete reorganisation: unified structure, expanded and clarified coding style (scope-based variable naming, comment rules, file layout order, naming convention table), removed redundancy, converted tables to AsciiDoc format.

v2.0

Updated for v0.28: added crypto subsystem, kernel module system, virtual memory layout, expanded architecture section, updated debugging with self-test details.

v1.3

Added Git version control section.

v1.2

Added structures for other components including welcome message and Git version controls.

v1.1

Clarified HAL assembly strategy for multi-architecture support, documented 3-space indentation.

v1.0

Documented initial coding style and conventions.