// HACKER NEWS — CYBERSECURITY
How to bring up the Linux Kernel on a new platform
A guide to bringing up the Linux Kernel and a userspace application on a new hardware (or in this case, a self-made emulator)
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:
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 interfaceSupervisor Binary InterfaceBasically 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.
On a real system, none of this would be necessary of course since the hardware would already be there. Instead of mapping the peripherals into the CPU’s address space, you would instead read the datasheet of your SoC to figure out where the peripherals are mapped and then go from there to the next step. Also instead of simply being able to load the Kernel, Device Tree and initramfs into the RAM, you’d generally use a bootloader such as u-boot to load everything from some non-volatile storage medium such as a NOR Flash or an eMMC.
Using this emulator, the final hardware definition looks like this:
This, of course, currently does absolutely nothing since there’s no code for it to execute. To get Linux running, we need to first configure and compile the Linux Kernel for our platform and then populate those LinuxKernel, DeviceTreeBlob and InitRamFs arrays with the correct data.
To compile anything to run on the target, the first thing we need is a toolchain. Compiling it by hand is generally a bit of a pain since you need to get the correct version of various different tools and libraries and then configure and compile them in the correct order (sometimes even multiple times with different configurations!). Luckily for us though, the crosstool-ng Project exists and handles all of that for us. With it we can simply configure the toolchain we need and then let it do all the work.