NixOS in a gameboy shell

Introduction

I built a retro gaming handheld based on a raspberry compute module 3 sometime ago. It was a super fun project. I often think my work as a developper misses a bit of manual work. Something that I love to do. This project was great for that reason as it involved a bit of soldering, 3D printing, hot gluing and precise dremmelling.

Anyway, after I built it, I installed a custom Linux distribution based on retropie made by the author of the board. It’s a bit rough in the edge. But it’s been working fine so far, can’t complain.

Unfortunately, after a couple of years, you know, software gets out of date and that distribution needed to be maintained. Seeing that the latest image for retropie itself is from March 2022, what can you expect from a super niche distribution built on top of it? Not much, only a couple of forks here and there, but nothing actively maintained.

I wanted to tackle this issue and bring the latest and greatest software to this handheld I built myself which has been sitting on my desk for too long. Additionally, this is one of the last piece of hardware I have not nixified yet.

So here am I, installing NixOS on a gameboy handheld. How cool is that?

I named the project circuix-sword based on the name of the board: Circuit-Sword. If by chance you are one of the lucky few to have one of those, you can now run NixOS on it. It is very easy. Get started by downloading the latest image from the releases page. Then burn it on an SD card, and you are ready to go. All the instructions are in the README.md of the repository.

While working on this project, I ran into countless issues, which is part of the reasons I wanted to do it in the first place. I will describe only a few of them here in chronological order.

Boot the system

This one wasn’t too hard, I just downloaded the latest NixOS image for aarch64, and it was able to boot on the CM3 out of the box. I just plugged a screen to the HDMI out, a keyboard to the USB port, and I was ready to go.

WiFi

There is not much you can do without internet. I first reached out for a USB ethernet adapter I had around. It is good enough for hacking but not great for day to day use.

Hopefully, the Circuit-Sword has a WiFi chip on board. It is a Realtek 8723bs but requires two things for it to work properly:

  • The right firmware. This is solved using the option: hardware.enableRedistributableFirmware = true; The closure induced by this option is quite big however (not less than 589 MiB). But we will address this issue later on.
  • The sdio overlay. This requires messing up with the device tree. NixOS doesn’t provide a nice solution for that yet. But I found this one which works good enough for me.

Screen, first attempt

Next, I needed to get the built-in screen working. The Circuit-Sword has a 320x240 DPI screen. Making it work was just a matter of copying the right lines from the original distribution in the config.txt file.

While this worked fine for the console output, it does not for graphics. The drivers used in the original distribution have been deprecated to be replaced by the KMS DRM drivers.

Screen, second attempt

Ok, now let’s make those KMS DRM drivers work.

First, I needed to mess again with the device tree and enabled the vc4-kms-v3d overlay, which is the KMS driver. I also needed the vc4-kms-dpi-generic overlay to drive the DPI screen. That second overlay is especially tricky to get right as it needs a lot of parameters to work properly. One can map the original parameters from the config.txt file to the new format. But the documentation is not very clear, and it took me a fair deal of tinkering to get it right.

But wait, this is not enough. Now the SDL library needs to support the KMS DRM driver too. I needed to tweak slightly the compilation of SDL to enable it.

At this point, I was able to run retroarch and start games, that was already a big achievement!

Sound

The sound card is a MicroII chip. For some reason, it is stuck at a very high volume. The Linux module needs to be patched for it to work properly.

The cs-hud service

The cs-hud service is a custom service written in C especially for the board.

  • It handles the safe-shutdown circuit
  • When pressing the special mode button, it shows a help screen on top of the framebuffer. This screen shows the keyboard shortcuts along the current state: the levels of the volume, brightness and battery.

I integrated the source code in the git repo, created the derivation to build it and wrote the systemd service to run it. Standard NixOS stuff so far.

Unfortunately, with the new KMS setup, it is not possible to write an image on top of the framebuffer anymore. I then hacked the original code and removed pretty much everything related to that OSD feature. I kept the safe-shutdown handling, the brightness and volume control, and the battery level management.

On the way, I lost the ability to see all the info from that help screen. I can cope without it most of the time. But at some point I will definitely need to be able to see the battery level.

In the next section, I will explain how I solved that issue.

retroarch

The original distribution runs emulationstation. At first, I tried to make it work in NixOS. But soon I realized that emulationstation is actually some kind of frontend on top of retroarch. To make things simpler, I decided to just use plain retroarch directly, at least for a start. That project is involved enough already.

I have made two quality of life improvement changes to retroarch:

  • I fixed the NetworkManager WiFi driver. It is now possible to set up the WiFi using the gamepad itself which is great as it wasn’t possible in the original distribution (merged upstream).
  • I exposed the battery state to a unix socket using cs-hud and made it available in retroarch. This way, I can see the battery level in the retroarch interface. I keep that change in my fork as it has no chance to be merged upstream.

Closure size optimization

At this point, I had a working system, but it was way bigger than it should be: 1.81 GiB for the image of the version v0.0.2. I started to look at the closure to see what I could remove. This is achieved by tweaking the compilation options of various packages using nix overlays.

The most obvious thing was everything related to the desktop, namely: xorg, wayland, pulseaudio, pipewire and so on. gtk was especially difficult to get rid of. NetworkManager pulls it in the openconnect plugin but removing all its plugins was not enough for some reason. The solution was to get rid of openconnect itself.

The mesa library was pretty big as well (197 MiB). I trimmed it down to 20 MiB.

The redistribuable firmware derivation is about 589 MiB for only one single firmware needed! I did my own derivation for the firmware I need. The resulting derivation is now 41 KiB. That’s much better!

The retroarch assets derivation is still huge (494 MiB) but I’m ok with that. I don’t want to track down every single asset and check if it is needed or not.

I wanted to get rid of perl as well. But it didn’t work out nicely.

The final image is now around 800 MiB, under 1 GiB, which is not great but good enough.

Firmware flash

The last piece of the puzzle is the gamepad firmware that runs on the arduino. I wanted the source to be part of the repo as well. I also made it sure it compiles and make it possible to flash it out of the box.

Conclusion

This has been a difficult but fun journey. I love this project. Building the handheld was a great experience and porting the software to NixOS was also super fun.

There are so many different parts to it, going from very low to high level that makes it super interesting. From the Linux drivers that must be patched, to fine-tuning the booting process, the software that needs to be patched as well, some custom components, the gamepad firmware, not to mention the closure size optimization.

Really, there is something for everyone.

I have found out that NixOS is incredibly good at gluing all these pieces together. The fact that it is possible to patch every single package in one single repository is a huge win. That whole project took me some time. But the final amount of code is fairly reasonable. And I am very confident that I will be able to maintain it for a very long time.

August 13, 2025