A multi-platform bare-metal operating system designed to teach low-level systems programming through hands-on hardware interaction.
| Board | SoC | Architecture | Implementation Status | Build Status |
|---|---|---|---|---|
| Raspberry Pi Zero 2W + GPi Case | BCM2710 | ARM | ✅ Complete | ✅ Passing |
| Raspberry Pi 4B / CM4 | BCM2711 | ARM | ✅ Complete | ✅ Passing |
| Raspberry Pi 5 / CM5 | BCM2712 | ARM | ✅ Complete | ✅ Passing |
| Orange Pi RV 2 | KYX1 | RISC-V | ✅ Complete | ✅ Passing |
| LattePanda Iota | N150 | x86_64 | ❌ InComplete | ❌ Failing |
| LattePanda MU Compute | N100 | x86_64 | ❌ InComplete | ❌ Failing |
| Milk-V Mars | Starfive JH7110 | RISC-V | ✅ Complete | ✅ Passing |
2026-02-12.09-59-44.mp4
tutorial-os/
├── hal/ # Hardware Abstraction Layer interfaces
│ ├── hal.h # Master include
│ ├── hal_types.h # Types, error codes, MMIO
│ ├── hal_platform.h # Platform info, temp, clocks
│ ├── hal_timer.h # Timing and delays
│ ├── hal_gpio.h # GPIO control
│ └── hal_display.h # Display initialization
│
│ # Each soc attempts to follow the same pattern for files
├── soc/ # SoC-specific implementations
│ ├── bcm2710/ # Raspberry Pi 3B, 3B+, 3A+, Zero 2 W, and CM3 devices
│ │ ├── bcm2710_mailbox.h # Mailbox Interface
│ │ ├── bcm2710_regs.h # Register Definitions
│ │ ├── boot_soc.S # SoC-Specific Boot Code
│ │ ├── display_dpi.c # Display Implementation (DPI/HDMI)
│ │ ├── gpio.c # GPIO Implementation
│ │ ├── linker.ld # Linker Script
│ │ ├── mailbox.c # Mailbox Implementation
│ │ ├── soc.mk # BCM2710 Configuration
│ │ ├── soc_init.c # Platform Initialization
│ │ └── timer.c # Timer Implementation
│ ├── bcm2711/ # Raspberry Pi 4, CM4, Pi 400
│ │ ├── bcm2711_mailbox.h # Mailbox Interface
│ │ ├── bcm2711_regs.h # Register Definitions
│ │ ├── boot_soc.S # SoC-Specific Boot Code
│ │ ├── display_dpi.c # Display Implementation (DPI/HDMI)
│ │ ├── gpio.c # GPIO Implementation
│ │ ├── linker.ld # Linker Script
│ │ ├── mailbox.c # Mailbox Implementation
│ │ ├── soc.mk # BCM2711 Configuration
│ │ ├── soc_init.c # Platform Initialization
│ │ └── timer.c # Timer Implementation
│ ├── bcm2712/ # Raspberry Pi 5, CM5
│ │ ├── bcm2712_mailbox.h # Mailbox Interface
│ │ ├── bcm2712_regs.h # Register Definitions
│ │ ├── boot_soc.S # SoC-Specific Boot Code
│ │ ├── display_dpi.c # Display Implementation (DPI/HDMI)
│ │ ├── gpio.c # GPIO Implementation
│ │ ├── linker.ld # Linker Script
│ │ ├── mailbox.c # Mailbox Implementation
│ │ ├── soc.mk # BCM2712 Configuration
│ │ ├── soc_init.c # Platform Initialization
│ │ └── timer.c # Timer Implementation
│ ├── kyx1/ # Orange Pi RV 2
│ │ ├── display_simplefb.c # Display Driver
│ │ ├── blobs # Uboot bins extracted from a build and dts files for device tree
│ │ ├── drivers # i2c, pmic_spm8821 and sbi driver code
│ │ ├── gpio.c # GPIO Implementation
│ │ ├── hal_platform_kyx1 # RISC-V equivalent of what soc/bcm2710/soc_init.c does for the Pi
│ │ ├── kyx1_cpu.h # CPU Operations
│ │ ├── kyx1_regs.h # Register Definitions
│ │ ├── linker.ld # Linker Script
│ │ ├── soc.mk # KYX1 Configuration
│ │ ├── soc_init.c # Platform Initialization
│ │ ├── timer.c # Timer Implementation
│ │ └── uart.c # UART Driver
│ ├── jh7110/ # Milk-V Mars
│ │ ├── display_simplefb.c # Display Driver
│ │ ├── blobs # dtbs files for device tree
│ │ ├── gpio.c # GPIO Implementation
│ │ ├── hal_platform_jh7110 # RISC-V equivalent of what soc/bcm2710/soc_init.c does for the Pi
│ │ ├── jh7110_cpu.h # CPU Operations
│ │ ├── jh7110_regs.h # Register Definitions
│ │ ├── linker.ld # Linker Script
│ │ ├── soc.mk # jh7110 Configuration
│ │ ├── mmu.S # Sv39 Page Table Setup for JH7110
│ │ ├── soc_init.c # Platform Initialization
│ │ ├── timer.c # Timer Implementation
│ └ └── uart.c # UART Driver
│
├── board/ # Board-specific configurations
│ ├── rpi-zero2w-gpi/
│ │ ├── board.mk # Build configuration
│ │ └── boot/ # Boot files for SD card
│ │ ├── config.txt # VideoCore GPU config
│ │ └── BOOT_FILES.md # Instructions
│ │
│ ├── rpi-cm4-io/
│ │ ├── board.mk
│ │ └── boot/
│ │ ├── config.txt
│ │ └── BOOT_FILES.md
│ │
│ ├── milkv-mars/
│ │ ├── uEnv.txt
│ │ ├── board.mk
│ │ ├── DEPLOY.md
│ │ └── mkimage.sh # creates the img with uboot configuration
│ │
│ └── orangepi-rv2/
│ ├── env_k1-x.txt
│ ├── board.mk
│ ├── boot.cmd
│ ├── DEPLOY.md
│ └── mkimage.sh # creates the img with uboot configuration
│
├── boot/ # Core assembly entry points
│ ├── arm64/
│ │ ├── cache.S # Cache Maintenance Functions
│ │ ├── common_init.S # Common Post-SoC Initialization
│ │ ├── entry.S # Entry Point
│ │ └── vectors.S # Exception Vector Table
│ ├── riscv64/
│ │ ├── cache.S # Cache Maintenance Functions
│ │ ├── common_init.S # Common Post-SoC Initialization
│ │ ├── entry.S # Entry Point
│ │ └── vectors.S # Exception Vector Table
│ ├── x86_64/
│ │ ├── cache.S # Cache Maintenance Functions
│ │ ├── common_init.S # Common Post-SoC Initialization
│ │ ├── entry.S # Entry Point
│ └ └── vectors.S # Exception Vector Table
│
├── common/ # Shared (less than) minimal libc and mmio
│ ├── mmio.h # Memory-Mapped I/O and System Primitives
│ ├── string.c # Memory and String Functions
│ ├── string.h # String and Memory Function Declarations
│ └── types.h # Type Definitions
│
├── drivers/ # Portable drivers
│ ├── audio/ # Core Audio System Drivers
│ │ ├── audio.c # PWM Audio Driver Implementation
│ │ └── audio.h # PWM Audio Driver Definitions
│ ├── framebuffer/ # Drawing Definitions
│ │ ├── framebuffer.c # 32-bit ARGB8888 Framebuffer Driver
│ │ └── framebuffer.h # Framebuffer definitions
│ ├── sdcard/ # SD Card Driver
│ │ ├── sdhost.h # SD Card Driver via SDHOST Controller
│ │ └── sdhost.c # SD Card Driver Implementation
│ ├── usb/ # USB Host Driver
│ │ ├── usb_host.h # DWC2 USB Host Controller Driver Definition
│ └ └── usb_host.c # DWC2 USB Host Controller Implementations
│
├── kernel/ # Kernel code
│ └── main.c # Main application entry point
│
├── memory/ # Memory management
│ ├── allocator.h # TLSF-Inspired Memory Allocator Declaration
│ └── allocator.c # TLSF-Inspired Memory Allocator
│
├── ui/ # UI System
│ ├── core/ # Core UI Canvas and Type definitions
│ │ ├── ui_canvas.h # Canvas and Text Renderer Interfaces
│ │ └── ui_types.h # Core UI Type Definitions
│ ├── themes/ # UI Theme System
│ │ └── ui_theme.h # UI Theme System definitions
│ ├── widgets/ # Reusable UI Widget Functions
│ │ ├── ui_widgets.h # UI Widget definitions
│ └ └── ui_widgets.c # UI Widget Implementations
│
├── build.sh # Build on Linux / MacOS
├── build.bat # Build on Windows
├── docker-build.sh # Build system
├── Dockerfile # Build system
├── Makefile # Build system
└── README.md # This file
# Build for Raspberry Pi Zero 2W with GPi Case
make LANG=c BOARD=rpi-zero2w-gpi
# Build for Raspberry Pi CM4
make LANG=rust BOARD=rpi-cm4-io
# Show build info
make info
# Clean
make clean
# Or use docker to build them all via the build commands
./build.bat
./build.sh
# For milk-v mars and orange pi rv 2, there is an additional build step required as they need uboot integration
docker run --rm -v ${PWD}:/src -w /src --entrypoint make tutorial-os-builder BOARD=milkv-mars image
docker run --rm -v ${PWD}:/src -w /src --entrypoint make tutorial-os-builder BOARD=orangepi-rv2 imageEach board has different boot requirements. See board/<name>/boot/BOOT_FILES.md for details.
Boot partition needs:
/boot/
├── bootcode.bin # (Pi Zero 2W only, not CM4)
├── start.elf # (or start4.elf for CM4)
├── fixup.dat # (or fixup4.dat for CM4)
├── config.txt # PROVIDED in board/xxx/boot/
└── kernel8.img # Tutorial-OS (build output)
Get firmware from: https://github.com/raspberrypi/firmware/tree/master/boot
Your fb_*() drawing functions don't change between platforms. The same main.c renders identically on ARM64, RISC-V64, and x86_64:
// This works on ALL platforms without a single #ifdef
fb_clear(fb, 0xFF000000);
fb_fill_rect(fb, 10, 10, 100, 50, 0xFFFFFFFF);
fb_draw_string_transparent(fb, 20, 20, "Hello World!", 0xFFFFFFFF);
ui_draw_panel(fb, panel, &theme, UI_PANEL_ELEVATED);Three fundamentally different paths to the same pixel on screen — the HAL makes them identical from main.c's perspective:
| Feature | BCM2710/2711/2712 (ARM64) | JH7110 (RISC-V64) | x86_64 (UEFI) |
|---|---|---|---|
| Boot | VideoCore GPU firmware | U-Boot + OpenSBI | UEFI firmware |
| Display init | Mailbox property tags | SimpleFB from DTB | GOP protocol |
| Framebuffer | Allocated by VideoCore | Pre-configured by U-Boot | Allocated by GOP |
| Cache flush | ARM DSB + cache ops | SiFive L2 Flush64 | x86 CLFLUSH |
| Timer | MMIO System Timer | RISC-V rdtime CSR |
HPET / TSC |
| Platform info | Mailbox queries | Fixed constants + DTB | CPUID + ACPI |
No runtime if (platform == X) checks. The build system selects the correct implementation at compile time:
# board/milkv-mars/board.mk
SOC := jh7110
include soc/$(SOC)/soc.mkHAL interface headers are defined before any implementation exists. Every platform implements the same contract — the drawing code never needs to know which side of the contract it's talking to.
HAL functions return hal_error_t:
hal_error_t err = hal_display_init(&fb);
if (HAL_FAILED(err)) {
if (err == HAL_ERROR_DISPLAY_MAILBOX) { ... }
}- Create SoC directory:
soc/newsoc/ - Implement HAL interfaces:
uart.c— UART driver (needed for debug output before display works)timer.c— Timer and delay functionsgpio.c— GPIO controlsoc_init.c— Platform initializationdisplay_*.c— Display driver
- Create register header:
newsoc_regs.h - Create build rules:
soc.mk - Create board config:
board/newboard/board.mk
Critical checklist for SimpleFB-based displays (U-Boot + device tree platforms):
After populating framebuffer_t in your display_init, always initialize the clip stack before returning:
fb->clip_depth = 0;
fb->clip_stack[0].x = 0;
fb->clip_stack[0].y = 0;
fb->clip_stack[0].w = info.width;
fb->clip_stack[0].h = info.height;
fb->dirty_count = 0;
fb->full_dirty = false;
fb->frame_count = 0;
fb->initialized = true;Skipping this will cause every fb_fill_rect, fb_draw_string, and widget call to silently draw nothing while fb_clear continues to work — making the display pipeline appear healthy when it isn't.
- Peripheral base:
0x3F000000 - GPIO pull requires GPPUD + GPPUDCLK sequence
- Display via VideoCore mailbox property tags
- DPI output on GPIO 0–27 (ALT2) for GPi Case
- Peripheral base:
0xFE000000 - GPIO pull via direct 2-bit registers (simpler than BCM2710)
- Same mailbox interface as BCM2710
- Peripheral base via RP1 southbridge
- HDMI routed through RP1 — do NOT configure DPI GPIO pins
- SET_DEPTH must be sent in a separate mailbox call before full allocation
- Verify returned pitch == width × 4; pitch == width × 2 means 16bpp allocation failed
- DRAM base:
0x40000000; kernel loads at0x40200000 - Framebuffer:
0xFE000000(confirmed via U-Bootbdinfo) - Display controller: DC8200 at
0x29400000 - L2 Cache flush via SiFive Flush64 at
0x02010200—fencealone is insufficient - U-Boot 2021.10 does not inject a
simple-framebufferDTB node — the hardcoded fallback path is permanent for this U-Boot version, not a temporary workaround - CPU: SiFive U74-MC, RV64IMAFDCBX — no Zicbom, no Svpbmt
- Boot via UEFI — PE/COFF EFI application at
\EFI\BOOT\BOOTX64.EFI - Framebuffer allocated via GOP (Graphics Output Protocol)
- No device tree — platform info from CPUID and ACPI tables
Educational use. See LICENSE file.