Many embedded systems these days run Linux as their operating system. Generally because it’s a great foundation to run anything you like on top of and because many great developers and manufacturers already took care of writing drivers for all kinds of hardware components.
Even though this makes building the finished project much easier, if you’re dealing with custom hardware, you will most likely still have to bring up the Linux kernel on your own initially. This post is about just that: Setting up the bare minimal to get Linux running on a new platform. In this case, the new platform is not a new PCB but a minimal, emulated RISC-V CPU.
Even though Linux is a very complex piece of software, it doesn’t actually have that many requirements to run. The only thing it really needs in terms of hardware is:
A CPU with an MMU Memory Management Unit
A piece of hardware on the SoC that handles translating virtual addresses to physical addresses, allowing for isolation between multiple different userspace processes and the Kernel
Enough RAM to, at least, load the Kernel, a Device Tree and an initramfs.
A periodically lapsing system clock timer
These things are what I ended up implementing in about 2000 lines of C++ code. For the CPU I implemented the RV32IMA instruction set which has all the things Linux needs to run. For the Hardware, I implemented a simple RAM peripheral, the SBI interface Supervisor Binary Interface
Basically a syscall-like interface that allows the Kernel to send request to Machine Mode (the CPU itself) to configure built-in functionality for timers and a UART peripheral based on the WD8250 chip which is well supported and is really simple to implement.
Documenting the entire process of writing the emulator is a bit out of scope for this post but the full implementation can be found here: WerWolv/riscv-emulator.
... continue reading