Guix on an ARM Board ==================== Increasingly people discovering Guix want to try it on an ARM board, instead of their x86 computer. There might be various reasons for that, from power consumption to security. In my case, I found these ARM boards practical for self-hosting, and I think the unique properties of GNU Guix are making it very suitable for that purpose. I have installed GNU Guix on a Cubietruck, so my examples below will be about that board. However, you should be able to change the examples for your own use case. Installing the Guix System on an ARM board is not as easy as installing it on an x86 desktop computer: there is no installation image. However, Guix supports ARM (you can even run it on your [Android phone!](http://guix.gnu.org/blog/2018/guix-on-android/) and can be installed on a foreign distribution running on that architecture. The trick is to use the Guix installed on that foreign distribution to initialize the Guix System. As we have previously [mentionned](https://guix.gnu.org/blog/2017/porting-guixsd-to-armv7/) it is possible to generate an installation image yourself, if your board is supported. There is also some work on integrating [buildroot](https://git.savannah.gnu.org/cgit/guix.git/log/?h=wip-buildroot) with Guix, so that all of these manual steps will no longer be necessary. Most boards can be booted from an existing GNU+Linux distribution. You will need to install a distribution (any of them) and [install](http://guix.gnu.org/manual/en/html_node/Binary-Installation.html) GNU Guix on it, using e.g. the installer script. Then, my plan was to install the Guix System on an external SSD drive, instead of the SD card, but we will see that both are perfectly possible. The first part of the article will focus on creating a proper U-Boot configuration and an operating system declaration that suits your board. The second part of this article will focus on the installation procedure, when there is no installer working for your system. Writing a configuration file for an ARM board --------------------------------------------- A configuration file for an ARM board is not very different from a configuration file for a desktop or a server running on another architecture. However, most boards use the U-Boot bootloader and require some less common modules to be available at boot time. ### The root file system First of all, you should decide where your root file system is going to be installed. In my case, I wanted to install is on the external SSD, so I chose it: ```scheme (file-systems (cons* (file-system (mount-point "/") (device "/dev/sda1") (type "ext4")) %base-file-systems)) ``` If you instead want to install the root file system on an SD card, you'll need to find its device name, usually `/dev/mmcblk0` and the partition number. The device corresponding to the first partition should be `/dev/mmcblk0p1`. In that case, you would have: ```scheme (file-systems (cons* (file-system (mount-point "/") (device "/dev/mmcblk0p1") (type "ext4")) %base-file-systems)) ``` ### The bootloader Contrary to grub, there are multiple variants of U-Boot, one per board type. The installation procedure for U-Boot is also somewhat specific to the board, so there are two things that you need to take care of: the U-Boot package and the bootloader declaration. You can also use the default U-Boot-bootloader to install a configuration, and let any existing U-Boot do its job. Guix already defines a few U-Boot based bootloaders, such as `u-boot-a20-olinuxino-lime-bootloader` or `u-boot-pine64-plus-bootloader` among others. If your board already has a `u-boot-*-bootloader` defined in `(gnu bootloader u-boot)`, you're lucky and you can skip this part of the article! Otherwise, maybe the bootloader package is defined in `(gnu packages bootloaders)`, such as the `u-boot-cubietruck` package. If so, you're a bit lucky and you can skip creating your own package definition. If your board doesn't have a `u-boot-*` package defined, you can create one. It could be as simple as `(make-u-boot-package "Cubietruck" "arm-linux-gnueabihf")`. The first argument is the board name, as expected by the U-Boot build sysetem. The second argument is the target triplet that corresponds to the architecture of the board. You should refer to the documentation of your board for selecting the correct values. If you're really unlucky, you'll need to do some extra work to make the U-Boot package you just created work, as is the case for the `u-boot-puma-rk3399` for instance: it needs additional phases to install firmware. You can add the package definition to your operating system configuration file like so, before the operating-system declaration: ```scheme (use-modules (gnu packages bootloaders)) (define u-boot-my-board (make-u-boot-package "Myboard" "arm-linux-gnueabihf")) (operating-system [...]) ``` Then, you need to define the bootloader. A bootloader is a structure that has a name, a package, an installer, a configuration file and a configuration file generator. Fortunately, Guix already defines a base U-Boot bootloader, so we can inherit from it and only redefine a few things. The Cubietruck happens to be based on an allwinner core, for which there is already a U-Boot bootloader definition `u-boot-allwinner-bootloader`. This bootloader is not usable as is for the Cubietruck, but it defines most of what we need. In order to get a proper bootloader for the Cubietruck, we define a new bootloader based on the Allwinner bootloader definition: ```scheme (define u-boot-cubietruck-bootloader (bootloader (inherit u-boot-allwinner-bootloader) (package u-boot-cubietruck))) ``` Now that we have our definitions, we can choose where to install the bootloader. In the case of the Cubietruck, I decided to install it on the SD card, because it cannot boot from the SSD directly. Refer to your board documentation to make sure you install U-Boot on a bootable device. As we said earlier, the SD card is `/dev/mmcblk0` on my device. We can now put everything together like so: ```scheme (use-modules (gnu packages bootloaders)) (define u-boot-cubietruck (make-u-boot-package "Cubietruck" "arm-linux-gnueabihf")) ;; u-boot-allwinner-bootloader is not exported by (gnu bootloader u-boot) so ;; we use @@ to get it. (@ (module) variable) means: get the value of "variable" ;; as defined (and exported) in (module). (@@ (module) variable) is the same, but ;; it doesn't care whether it is exported or not. ;; A note of caution though: this is not part of the public API and is subject to ;; change at any time, without warning. If you need to use it for your board, ;; consider contributing your bootloader instead. (define u-boot-allwinner-bootloader (@@ (gnu bootloader u-boot) u-boot-allwinner-bootloader)) (define u-boot-cubietruck-bootloader (bootloader (inherit u-boot-allwinner-bootloader) (package u-boot-cubietruck))) (operating-system [...] (bootloader (bootloader-configuration (target "/dev/mmcblk0") (bootloader u-boot-cubietruck-bootloader))) [...]) ``` ### The kernel modules In order for Guix to be able to load the system from the initial RAM disk (initrd), it will probably need to load some modules, especially to access the root file system. In my case, the SSD is on an AHCI device, so I need a driver for it. The kernel defines `ahci_sunxi` for that device on any sunxi board. The SD card itself also requires two drivers: `sunxi-mmc` and `sd_mod`. Your own board may need other kernel modules to boot properly, however it is hard to discover them. Guix can tell you when a module is missing in your configuration file if it is loaded as a module. Most distributions however build these modules in the kernel directly, so Guix cannot detect them reliably. Another way to find what drivers might be needed is to look at the output of `dmesg`. The most important modules are modules used for the hardware you'll be installing the root file system on. You'll find messages such as: ``` [ 5.193684] sunxi-mmc 1c0f000.mmc: Got CD GPIO [ 5.219697] sunxi-mmc 1c0f000.mmc: initialized, max. request size: 16384 KB [ 5.221819] sunxi-mmc 1c12000.mmc: allocated mmc-pwrseq [ 5.245620] sunxi-mmc 1c12000.mmc: initialized, max. request size: 16384 KB [ 5.255341] mmc0: host does not support reading read-only switch, assuming write-enable [ 5.265310] mmc0: new high speed SDHC card at address 0007 [ 5.268723] mmcblk0: mmc0:0007 SD32G 29.9 GiB ``` or ``` [ 5.614961] ahci-sunxi 1c18000.sata: controller can't do PMP, turning off CAP_PMP [ 5.614981] ahci-sunxi 1c18000.sata: forcing PORTS_IMPL to 0x1 [ 5.615067] ahci-sunxi 1c18000.sata: AHCI 0001.0100 32 slots 1 ports 3 Gbps 0x1 impl platform mode [ 5.615083] ahci-sunxi 1c18000.sata: flags: ncq sntf pm led clo only pio slum part ccc [ 5.616840] scsi host0: ahci-sunxi [ 5.617458] ata1: SATA max UDMA/133 mmio [mem 0x01c18000-0x01c18fff] port 0x100 irq 37 [ 5.933494] ata1: SATA link up 3.0 Gbps (SStatus 123 SControl 300) ``` Also note that module names are not consistent between what Guix expects and what is printed by dmesg, especially when the name contains a "-" or a "_". You will find the correct file name by building (or using a substitute for) linux-libre beforehand: ``` find `guix build linux-libre`/lib/modules -name '*mmc*' ``` Here, I could find a file named "kernel/drivers/mmc/host/sunxi-mmc.ko", hence the module name `sunxi-mmc`. For the other driver, I found a "kernel/drivers/ata/ahci_sunxi.ko", hence the name `ahci_sunxi`, even if dmesg suggested `ahci-sunxi` (notice the "_" and the "-" difference). Once you have found the modules you want to load before mounting the root partition, you can add them to your operating-system declaration file: ```scheme (initrd-modules (cons* "sunxi-mmc" "sd_mod" "ahci_sunxi" %base-initrd-modules)) ``` Installing the Guix System -------------------------- ### Installing on another drive In my case, I wanted to install the system on an external SSD, while the currently running foreign distribution was running from the SD card. What is nice with this setup is that, in case of real trouble (your SSD caught fire or broke), you can still boot from the old foreign system with an installed Guix and all your tools by re-flashing only the bootloader. In this scenario, we use the foreign system as we would use the installer ISO, using the [manual installation procedure](http://guix.gnu.org/manual/en/html_node/Proceeding-with-the-Installation.html) described in the manual. Essentially, you have to partition your SSD to your liking, format your new partitions and make sure to reference the correct partition for the root file system in your configuration file. Then, initialize the system with: ```bash mount /dev/sda1 /mnt mkdir /mnt/etc $EDITOR /mnt/etc/config.scm # create the configuration file guix system init /mnt/etc/config.scm /mnt ``` You can now reboot and enjoy your new Guix System! Note that we didn't need to use the cow-store service that is mentionned in the manual. That is because builds and downloads happen in the store of the foreign distribution, not in memory. Guix will copy necessary files to the target. ### Installing on the same drive Another option is to install the Guix System over the existing foreign distribution, replacing it entirely. Note that the root filesystem for the new Guix System is the current root filesystem, so no need to mount it. The following will initialize your system: ```bash $EDITOR /etc/config.scm # create the configuration file guix system init /etc/config.scm / ``` Make sure to remove the files from the old system. You should at least get rid of the old `/etc` directory, like so: ```bash mv /etc{,.bak} mkdir /etc ``` Make sure there is an empty /etc, or the new system won't boot properly. You can copy your config.scm to the new `/etc` directory. You can now reboot and enjoy your new Guix System!