Grinch is a minimalist operating system, developed mainly for educational and testing purposes. It targets RISC-V as its primary architecture and also ships an ARM64 (AArch64) port. Grinch runs userspace processes in ELF format and, on RISC-V, can recursively boot itself as a virtual machine through its built-in Virtual Machine Monitor.
Userland applications get basic POSIX support through grinch's own minimalist libc; the syscall interface stays as close to Linux as reasonably possible.
Anyway, don't expect anything to work.
- Device-tree based hardware discovery
- Simple scheduler
- ELF userland applications with a minimalist, Linux-compatible libc
- Interactive shell (
gsh) and a small set of userland utilities - Device-tree probed driver model
- Virtual filesystem layer (tmpfs, devfs, initrd)
- PCIe host bridge (generic ECAM)
- Framebuffer output
- Recursive Virtual Machine Monitor (RISC-V)
- GCOV coverage support
- RV64 and RV32
- SV32, SV39 and SV48 paging
- SV39x and SV48x two-stage paging for guests
- SMP (RV64 only)
- RISC-V platform timer
- PLIC and APLIC interrupt controllers
- SBI console and SBI-based platform services
- Hypervisor (H) extension with a self-virtualising VMM
- EL1 kernel
- 4-level MMU (L0-L3) with 1 GB and 2 MB block mappings
- ASID-tagged address spaces (TTBR0/TTBR1)
- SMP via device-tree CPU discovery and PSCI (SMC/HVC) bring-up
- ARM generic timer
- GIC-v2 interrupt controller
- ARM semihosting early console
- Serial and console
- 8250/16550 UART
- AXI UART Lite
- LiteUART (LiteX)
- APBUART
- PL011 (ARM)
- RISC-V SBI console
- ARM semihosting console
- Interrupt controllers
- RISC-V PLIC and APLIC
- ARM GIC-v2
- PCIe host bridge (generic ECAM)
- Framebuffer
- Bochs VGA
- Host-backed framebuffer
- RISC-V
- riscv32: Virtual target (QEMU virt)
- riscv64: Virtual target (QEMU virt), StarFive VisionFive 2
- ARM64
- Virtual target (QEMU virt)
All build- and runtime dependencies - the RISC-V and ARM64 cross toolchains,
QEMU, dtc, cpio and Python - are provided by the Nix flake shipped with
grinch. The recommended way to get a working environment is Nix together
with direnv:
git clone https://github.com/lfd/grinch.git
cd grinch
git submodule update --init --recursive
direnv allow
direnv picks up the .envrc (use flake) and drops you into a shell with the
complete toolchain. Without direnv, enter the environment manually:
nix develop
For compiling grinch, simply run:
make
For verbose compiler output, run:
make V=1
This will create grinch.bin and user/initrd.cpio. grinch.bin is the
loadable kernel image, which is directly loadable via Qemu on virtual targets,
or via U-Boot on real platforms. user/initrd.cpio contains userland
applications, as well as grinch itself (grinch is able to recursively boot
itself as virtual machine).
Options are declared in the gConfig file at the top of the source tree. Their
values live in config.mk in the build directory, and config.h is derived
from them and included into every translation unit.
On the first invocation (or after make defconfig), configuration tunables
passed on the command line seed config.mk:
make O=build ARCH=riscv32 OPT=speed CONFIG_DEBUG_OUTPUT=y
OPT selects an optimisation level by name: none (-O0), speed (-O2),
size (-Os) or release (-O3).
Once config.mk exists it is the source of truth and passing configuration
tunables (ARCH, PLATFORM, OPT, CONFIG_*) on the command line is
rejected. To change a setting afterwards:
make menuconfig # edit interactively
make oldconfig # keep the values, pick up newly declared options
make defconfig # reset everything to its default
Editing config.mk by hand works as well. make defconfig optionally seeds new
values from the same command line. make mrproper discards config.mk along
with all build output.
Presets for the configurations that are regularly built live in configs/. Each
holds only the values that define it, so everything else follows its default.
Apply one by name:
make riscv32_defconfig
make riscv64_gcov_defconfig
A value that no default could have produced counts as a deliberate override and
stays. Everything else follows its default, so changing ARCH switches
CROSS_COMPILE, PLATFORM and the driver selection along with it.
V=1 and QEMU_* variables are always accepted on the command line regardless
of whether config.mk exists.
For out-of-tree builds, pass O= (tunables are seeded into build/config.mk
on the first invocation, as shown above). A small Makefile shim is generated
in build/, so afterwards you can work from the build directory directly:
cd build
make
make qemu
make mrproper
The source tree is checked for in-tree build artefacts and the OOT build
refuses to proceed if any are found, to avoid VPATH silently picking them
up. Run make mrproper in the source tree to clean it.
For running grinch inside QEMU, simply run:
make qemu
For debugging grinch, you can attach with a debugger:
make qemudb
In a second terminal, run:
make debug
Grinch is also able to boot on Qemu via U-Boot. For booting grinch via U-Boot in Qemu, type:
make qemuu
To pass parameters to grinch on real board, you can use the bootargs variable
in U-Boot. Grinch parses those parameters. To pass parameters in Qemu, use:
make qemu QEMU_APPEND='"arg1=val1 arg2=val2"'
You can also adjust the number of Qemus CPUs:
make qemu QEMU_CPUS=4
Basic features:
| Parameter | Values | Description |
|---|---|---|
| timer_hz | int | Timer frequency |
| loglevel | int | loglevel. Highest=0, Default=1 |
| kheap_size | int | Kernel Heap size (e.g., 8M) |
| ioremap_size | int | Usable I/O remap size (e.g., 64M) |
| console | str | boot console (ttyS0) |
| init | str | init executable |
Development features:
| Parameter | Values | Description |
|---|---|---|
| memtest | / | Do memory test |
| malloc_fsck | / | Run sanity checker for kalloc |
| ttp_maxevents | int | No of maxevents for timed TPs |
Automated tests drive QEMU from Python over a TCP serial socket, with
the HMP monitor used for guaranteed shutdown. tests/run.py builds
each variant, runs the applicable tests, and prints a summary table.
A variant is one preset from configs/ at one optimisation level, named
<preset>-<opt>. Adding a preset therefore adds coverage without touching
the test runner.
To run everything:
./tests/run.py
To build and run a single variant:
./tests/run.py riscv64-speed
Variant names are globs, so ./tests/run.py 'riscv32-*' covers one preset
across all optimisation levels.
To build all variants without running tests:
./tests/run.py --build
To run tests against already-built variants:
./tests/run.py --run
By default, output goes to build/test/. Use -o DIR to change it:
./tests/run.py -o /tmp/grinch-tests
Grinch is developed by Ralf Ramsauer at OTH Regensburg. It is licensed under GPLv2. Parts of grinch are copied from Linux kernel sources. These parts involve:
- ctype implementation
- List implementation (list.h)
- Auxiliary headers (compiler_attributes.h, const.h, minmax.h)
- vsprintf support
- ELF headers
- strtox
- rwonce
