Alpine on the Olimex A20 OLinuXino Lime2

Update 2023-01

Since this whole process is long, arduous and repetitive, I have written a script that allows you to do this fully automatically, either with pre-supplied options or interactively:
Codeberg.org - alpine-a20-olinuxino-lime2

The Olimex OLinuXino LIME2 is an open hardware board with 1GB RAM, 1Gbps LAN and optional onboard eMMC/NAND storage and SPI flash. This guide will explain how to install Alpine Linux 3.17.0 on it, including how to compile the U-Boot bootloader.

Note: Throughout this guide I use the command ‘doas’ to run commands with root privileges. Your distribution might instead be using ‘sudo’, so simply replace ‘doas’ with ‘sudo’ if it does not work.

Short version of this article for advanced readers:
Alpine on the Olimex OLinuXino Lime2 - short version

Preparation

Finding your μSD card

First, we have to figure out which device is our μSD Card. You can use the ’lsblk’ utility for this, in my case its output looks something like this:

$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda      8:0    0 465.8G  0 disk 
├─sda1   8:1    0   512M  0 part /boot/efi
├─sda2   8:2    0   512M  0 part /boot
└─sda3   8:3    0   232G  0 part 
(...)
sdc      8:32   1  29.7G  0 disk 
└─sdc1   8:33   1     7G  0 part 

I am using a 32GB μSD card and it is showing up with the name “sdc” and a size of 29.7GiB. Your card could also show up with a name such as “mmcblk0”, but in any case, you should be absolutely certain that you know which one is your μSD Card before proceeding because we will be overwriting the contents of whichever device you select.

The full path of your card is “/dev/NAME”, in my case “/dev/sdc”. I will refer to this as “/dev/xyz” from now on, you should replace it with the path of your card.

Wiping the start of your card

On storage devices there is a small area before the start of the first partition that is used to keep the partition table and sometimes other things like a bootloader. To make sure that the bootloader we will flash to the card will not conflict with any previous bootloader, we overwrite the first 1 MiB of the card with zeros:

$ doas dd if=/dev/zero of=/dev/xyz bs=1M count=1
1048576+0 records in
1048576+0 records out

We are overwriting 1 MiB because the first partiton starts in sector 2048 of the card, so with a sector size of 512 Bytes, there are 512 * 2048 = 1048576 Bytes before the first partition, which is 1 MiB.

Partitioning

For partitioning, I will use the ‘sfdisk’ utility. We can use it to create partitions by giving it an input string over stdin. Each line in the string corresponds to a new partition, with few exceptions that are mentioned in the sfdisk(8) manpage. We can pass several lines in a single string by using printf with “\n”.

In many cases one big partiton will be sufficient, but if you want to have a writable partition for some directories alongside the read-only partition for everything else, you can create more partitions to suit your preferences.

To create a single bootable partition that takes up the entire card:

$ printf "size=-, bootable" | doas sfdisk --wipe-partitions always /dev/xyz

To create multiple partitions, in this case the first one is 4GiB in size and bootable, the second one 2GiB in size, and the third one taking the rest of the space:

$ printf "size=4GiB, bootable\nsize=2GiB\nsize=-" | doas sfdisk --wipe-partitions always /dev/xyz

The property “size=-” means that sfdisk will go with the default - the default is to use all remaining space. We use the “–wipe-partitions always” option to make sure that signatures of previously existing partitions are completely wiped to avoid conflicts.

Creating the filesystems

Now we still need to create the file systems on these partitions. Every partition has a path, for example “/dev/sdc1” or “/dev/mmcblk0p2”. If you want to see the paths of the partitions you created, you can run ‘doas sfdisk -l /dev/xyz’.

To create an Ext4 filesystem, we use the ‘mkfs.ext4’ command. Please replace “/dev/xyz1” with the correct path of your partition. If you created multiple partitions, do this for each of them.

$ doas mkfs.ext4 /dev/xyz1

U-Boot

U-Boot is the bootloader that will start Linux for us (simplified explanation). Since Alpine’s generic ARM release does not include a u-boot for our board, we will have to build it from source.

Building U-Boot requires some packages that will vary depending on which distribution you use. I will show you which ones are needed for Alpine, for other distributions please refer to U-Boot’s official documentation:
u-boot.readthedocs.io - Build U-Boot

Alternatively, you can use the instructions from the U-Boot wiki to build it on whichever system you are using. In this case, you can skip the section about installing dependencies because it will be different for you.

Installing dependencies and getting U-Boot

We need to install the following packages to build U-Boot for our board:

$ doas apk add alpine-sdk bc bison dtc flex gcc-arm-none-eabi linux-headers \
ncurses-dev openssl-dev perl python3 py3-setuptools python3-dev sdl2-dev swig 

Now we can download the source code of U-Boot. The latest release at the time of writing is v2022.10, so we will checkout that version of the code.

$ git clone https://source.denx.de/u-boot/u-boot.git
$ cd u-boot
$ git checkout v2022.10

Configuring U-Boot

The hardware of the A20 OLinuXino LIME2 has been revised multiple times throughout its existence, so depending on which revision you have, the configuration may need slight adjustments. The revision of your board is written on the front of the board, underneath the text “A20-OLinuXino-Lime2”. For my board, it says “Rev.K”. You can find details on which configuration parameters to use for which revision on the linux-sunxi wiki. Since the site seems to be down currently, I am including a link to web.archive.org aswell.
linux-sunxi.org - Olimex A20-OLinuXino-Lime2
web.archive.org - Olimex A20-OLinuXino-Lime2

Quite honestly it’s confusing and messy to figure out which options are ideal for which revision, so I recommend reading the “GMAC Quirks” section yourself. I will provide a brief summary of my understanding:

If your board has eMMC storage, edit “configs/A20-OLinuXino-Lime2-eMMC_defconfig”. Otherwise, edit “configs/A20-OLinuXino-Lime2_defconfig”.

For board revisions A-E:

For board revisions F-G2:

For board revisions H and later:

Since I own a board with eMMC storage and revision K, I will be using the eMMC configuration file and add “CONFIG_GMAC_TX_DELAY=4” to it, since the other two options were already present.

Compiling U-Boot

After we are done editing the defconfig file, we have to run one of these two commands depending on whether or not you have eMMC:

$ make A20-OLinuXino-Lime2_defconfig      # without eMMC
$ make A20-OLinuXino-Lime2-eMMC_defconfig # with eMMC

Then run ‘make’ with the correct cross-compile target to build u-boot. The name of this target will likely be different on other distributions, so please refer to the U-Boot documentation if you are not using Alpine.

$ CROSS_COMPILE=arm-none-eabi- make

Flashing U-Boot

After building u-boot, we get the file “u-boot-sunxi-with-spl.bin” as output, among other things. This file needs to be flashed to the μSD card with the following command:

$ doas dd if=u-boot-sunxi-with-spl.bin of=/dev/xyz bs=1024 seek=8

We can save the “u-boot-sunxi-with-spl.bin” file for the future, if we ever want to completely wipe the μSD card. U-Boot is now flashed to it and will be able to boot Linux. So next, we need to put Alpine Linux on the card.

Alpine Linux

First, we need to download the “Generic ARM” build for armv7 from the Alpine Linux website.
alpinelinux.org - Downloads

I simply copy the links and download the archive and its sha256 file with wget:

$ wget https://dl-cdn.alpinelinux.org/alpine/v3.17/releases/armv7/alpine-uboot-3.17.0-armv7.tar.gz
$ wget https://dl-cdn.alpinelinux.org/alpine/v3.17/releases/armv7/alpine-uboot-3.17.0-armv7.tar.gz.sha256

Then we can verify the integrity of the downloaded file:

$ sha256sum -c alpine-uboot-3.17.0-armv7.tar.gz.sha256
alpine-uboot-3.17.0-armv7.tar.gz: OK

If this does not say “OK” for your file, you need to re-download the archive because it got corrupted during the download.

Next, we mount the bootable Ext4 partition we created earlier. In my case, it is at the path “/dev/sdc1”, but it might be different for you, so remember to run ’lsblk’ or ‘doas sfdisk -l $sdcard’ if you are not sure.

$ mkdir mnt
$ doas mount $partition mnt/

Now we can extract the Alpine Linux archive onto the partition:

$ doas tar -xzf alpine-uboot-3.17.0-armv7.tar.gz -C mnt/

Since the u-boot binaries included in the archive are not compatible with our board, we can delete them:

$ doas rm -r mnt/u-boot

Now we need to copy the “u-boot.dtb” file to the partition. This file is basically a technical description of our board that informs the Linux kernel which drivers to load at boot.

$ doas cp u-boot/u-boot.dtb mnt/boot/

Finally, we need to edit the file “mnt/extlinux/extlinux.conf” to make sure it uses our “u-boot.dtb” file instead of the default:

TIMEOUT 10
PROMPT 1
DEFAULT lts

LABEL lts
MENU LABEL Linux lts
KERNEL /boot/vmlinuz-lts
INITRD /boot/initramfs-lts
FDT /boot/u-boot.dtb
APPEND modules=loop,squashfs,sd-mod,usb-storage quiet 

Now we can unmount the partition and we are ready to boot for the first time!

$ doas sync
$ doas umount mnt/

Running Alpine Linux

Of course, first of all we insert the μSD card into the slot on the OLinuXino. Now we have two options for working with the board, either we connect a monitor to the HDMI port and a USB keyboard, or we connect to the board through its serial interface with a cable like this:
olimex.com - USB to serial cable (female)

I will briefly explain how to connect to the board with this serial adapter, if you use the monitor/keyboard approach then you can skip to the next section.

First, you need to figure out which wire is which. The serial connection has one wire for ground (GND), one for transmitting (TX) and one for receiving (RX). In the case of the Olimex adapter it is GND=BLUE, RX=GREEN, TX=RED. The male pins on the board can be found right next to the ethernet port. Now you need to connect the GND wire to the GND pin on the board, the TX wire to the RX pin on the board, and the RX wire to the TX pin on the board. Make sure you don’t connect TX to TX and RX to RX!

Now we can connect the adapter to the computer. I like to use the ‘minicom’ program for serial connections, but others work too. Make sure you configure the program to use the serial device “/dev/ttyUSB0” with a baudrate of 115200 and 8N1 mode, with hardware flow control enabled and everything else disabled. In minicom you can get into the configuration with ‘doas minicom -s’ and save your settings with the “Save setup as dfl” option. Then you can close the settings and simply run ‘doas minicom’ to connect to the board. When you start the board now, you will see all of the board’s output on screen. To exit minicom, press Alt+Z to open its help menu and press X to exit.

If minicom says that /dev/ttyUSB0 does not exist, you can re-plug the serial adapter and run ‘doas dmesg | grep tty’ to see which device file it got assigned to and change the settings accordingly.

Running Alpine Linux

We have a few options for how we can install/use Alpine Linux. It is possible to install it like a traditional operating system on your board’s eMMC or a USB stick, but what may be more interesting is to run it in diskless mode.

In diskless mode, Alpine is loaded into RAM at boot and the μSD card is mounted read-only. This can significantly extend the lifespan of the μSD card and prevent corruption if power is lost, because nothing is written to the μSD card during normal operation and all changes are made in RAM.

Now you might be wondering how it is possible to make any changes if nothing is written to the μSD card and changes are lost on reboot. This is where the ’lbu’ utility comes in - with ’lbu’ you can save changes to the μSD card, so that they are restored when you reboot the system. This includes everything everything in “/etc” by default (with the exception of “/etc/init.d/”, but you can also configure ’lbu’ to back up other directories such as “/home” if needed for your usecase.

wiki.alpinelinux.org - Alpine local backup

If you still need a writable partition to store logs or something else, you can either create a partition on your μSD card as I described above, or create a partition on the internal eMMC (if your board has it) or a USB stick or external SSD/HDD that can be connected to the board. You can then mount this partition at “/var/log” or another location that you want to use for logging. Alternatively, you could send your logs to a different computer/server, but the details of this are up to you and outside of the scope of this article.

To actually install Alpine Linux, you can simply follow the existing installation guides on the wiki or just run ‘setup-alpine’ and go through all the questions it asks to setup your system.

wiki.alpinelinux.org - Installation

Conclusion

I hope that this article was able to give you a good overview on how to get Alpine Linux running on the Olimex A20 OLinuXino Lime2. If you have any trouble or any other questions or suggestions, feel free to send me an email to contact@regrow.earth!