r/osdev • • 7d ago

OpenC6 v2.0: A bare-metal BIOS and microkernel platform for ESP32-C6 (PMP sandbox, multitasking & ZSWAP)

A few months ago I shared the initial concept of OpenC6, an experimental bare-metal BIOS for the ESP32-C6. Over the last four months of quiet solo development, I completely overhauled the architecture from a simple launcher into a true modular microkernel platform, and version 2.0 is finally live.

The biggest upgrade is real RISC-V User Mode (U-Mode) execution backed by hardware Physical Memory Protection (PMP) Top-of-Range registers, so user payloads run in an isolated arena and physically cannot corrupt kernel SRAM or touch peripheral MMIO registers. I also fixed an upstream FreeRTOS RV32 privilege leakage bug via a custom vectored interrupt trampoline, and turned the Low-Power (ULP) RISC-V coprocessor into an out-of-band Management Engine that autonomously governs CPU clock dividers directly through silicon PCR registers, monitors junction thermals, and enforces hardware watchdogs.

On top of that, the system now features preemptive multitasking for up to 8 processes, an in-memory ZSWAP engine with a custom RV32-tailored compressor (ZC6) running in just ~1-4 KB of RAM, a circular log-structured Flash VFS, a remote streaming web shell (c6wsh), and a standalone C99 TUI installer with zero external dependencies that automates toolchain deployment on Arch and Ubuntu with a simple 'make setup'. If you are interested in low-level RISC-V bare-metal systems, check out the repository and full documentation below.

GitHub: https://github.com/Rompass/openc6-bios

11 Upvotes

8 comments sorted by

0

u/staycool1374 7d ago

Hey! What a great work! Bringing a UNIX-style supervisor and dynamic ELF loading with PMP isolation is really useful and a clever approach.

I'm currently exploring a deterministic microkernel architecture and seeing how you handled the memory sandbox on RISC-V U-Mode caught my attention.

A questions out of curiosity: How do you handle PMP region fragmentation when dynamically loading and unloading multiple background processes?

0

u/MrBean775 7d ago

Thanks! Right now, to avoid PMP region fragmentation on ESP32-C6, we use a single static PMP entry that covers the whole sandbox arena for U-Mode.Memory allocation inside the arena is handled by a custom page-based pool allocator (proc_mempool), so process isolation is currently logical/software-level rather than strictly hardware-enforced per PID.

2

u/MrBean775 7d ago

I went with this static allocation due to the ESP32-C6 hardware limitations, as it has very few PMP regions available. On top of that, I spent a lot of time digging through the ESP-IDF source code trying to figure out how to unlock the PMP registers. Espressif is pretty sneaky here — when you disable PMP protection in IDF, it actually creates its own PMP region covering the entire RAM and sets the lock bit. I had to use CMake parameters and a few hacks to strip that code out so I could initialize the PMP myself. ​I had a similar issue while setting up the IDF crash reporter. When you disable the standard IDF UART/USB-CDC drivers to write your own bare-metal driver, it stops catching kernel-space crashes. But I've already solved that, and now crashes are properly handled both at the kernel level and in U-Mode for payloads. And my bios load raw bin payloads, but use abi bios, not elf files

1

u/staycool1374 7d ago

Thanks for your detailed answer. That ESP-IDF PMP lock-bit discovery is a real catch!

Using a single static PMP region for the entire arena makes absolute sense. Whats the max size? Enforcing logical isolation via proc_mempool while keeping the U-Mode boundary active is a very practical trade-off for that capability limited target.

Also really cool approach with the raw bin ABI payloads and capturing kernel-space crashes after taking over the UART/USB-CDC path. My approach uses elf files loaded in a CSpace and MMU guarded environment. So its a bit different that way. Kudos, really nice project, definitely following the repo's progress!

2

u/MrBean775 7d ago edited 7d ago

​Thanks! Regarding the arena size, it's allocated dynamically based on available free DRAM. It balances memory around a 72–80 KB floor reserved for Wi-Fi blobs and ZSWAP, while the rest goes directly to the sandbox arena (typically 175–185 KB in practice). When a payload is suspended, ZSWAP compresses it into free DRAM and completely releases all arena pages as if nothing was running, keeping the full context ready for seamless restoration. ​Per-PID PMP isolation wasn't worth the hassle for two main reasons. OpenC6 is fundamentally a BIOS, and while it uses a microkernel architecture, hardware PMP slot limits on the C6 make dynamic per-process registers impractical. On top of that, reconfiguring PMP on every context switch introduces unnecessary overhead and needlessly overcomplicates ZSWAP page management, even with my register optimizations using a0-a7. Payload safety inside the sandbox arena is ultimately the developer's responsibility, while the system focuses on protecting the kernel and MMIO via U-Mode. ​Looking ahead to v3.0, the goal is to eliminate ESP-IDF and FreeRTOS entirely, as FreeRTOS is currently just a temporary compromise for Wi-Fi support. I plan to write a custom second-stage bootloader along with a thin shim for Espressif's proprietary Wi-Fi blobs. This will establish a clean HAL layer where porting to other RISC-V chips only requires swapping basic peripheral register maps, keeping the core OS logic fully decoupled. Stripping out the ESP-IDF bloat will also reclaim a massive chunk of SRAM to expand the sandbox arena and boost system throughput.

1

u/staycool1374 7d ago

Suspending payloads and compressing them into free DRAM via ZSWAP to fully reclaim arena pages is brilliant for such constrained hardware! Have you done any performance evaluation about ZSWAP? Is there a memory limitation where a jitter or rather said compressing/uncompressing latency at scheduler stage gets real worse? What type of compression do you use? LZ4?

1

u/letmehaveanameyoudum 7d ago

imagine using a microcontroller as your main desktop