Alpine on the Olimex 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:

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 that is plugged into an adapter, and it is showing up with the name "sdc" here, with a size of 29.7GiB. Your μSD Card could also show up with a name such as "mmcblk0" or "mmcblk1" etc, 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 "$sdcard" from now on.

Wiping the start of your card

Since the first partition on this μSD card cannot start before sector 2048 and the sector size is 512 Bytes, there are 512 * 2048 = 1048576 Bytes (which is 1 MiB) before the first partition. This is where the Bootloader will reside, among other things. So first, we wipe this first MiB to make sure that there is nothing left of any previous bootloaders that might have been on the card. We do this by overwriting it with zeros:

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

Partitioning

For partitioning, I will use the 'sfdisk' utility, which operates based on whatever string it gets as an input. This makes it the best option for a guide like this, but of course you are free to use different tools if you want.

First we create an empty disklabel, which will be MBR/dos in our case to keep things simple. GPT might also be possible, I did not look into it. We create an empty MBR/dos disklabel with the following command:

$ echo "label: dos" | doas sfdisk $sdcard

Now we create the partitions. A single Ext4 partition will be sufficient for many cases, but it is also possible to create multiple partitions (for example if you want to have a separate partition for logs). I will give examples for both options below.

Single partition

To create a single bootable Ext4 partition, we use the following command:

$ echo "type=83, bootable" | doas sfdisk --wipe-partitions always $sdcard

The "type" parameter specifies the partition type, in this case the hexadecimal number 0x83 corresponds to "Linux", which can be used for Ext4 partitions. You can list all possible partition types for MBR and their numbers with 'sfdisk -T --label dos'.

By default, sfdisk creates the partition start at the start of the storage and with the maximum possible size, so we do not need to specify the "start" or "size" parameters. If you wanted to make the partition 4GiB in size, you could use "size=4GiB, type=83, bootable". We also run sfdisk with the "--wipe-partitions always" option to make sure that the partition we create does not collide with signatures from partitions that were on our device before.

Multiple partitions

Multiple partitions can be created by giving multiple lines of input to sfdisk. If we want to create a bootable Ext4 partition with 4GiB size, followed by an Ext4 partition that takes the rest of the available space, we can do it like this:

$ printf "size=4GiB, type=83, bootable\ntype=83" | doas sfdisk --wipe-partitions always $sdcard

Please see above for explanations regarding the size and type of partitions, and the "--wipe-partitions" option. If you want to create additional partitions, this can be done by simply adding additional lines of input in the same format.

For more details about all possible options, please refer to the sfdisk(8) manpage.

Creating the filesystems

We have successfully partitioned the μSD card, but we still need to create the actual 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 $sdcard'.

To create an Ext4 filesystem, we use the 'mkfs.ext4' command. Please replace $partition with the correct path of your partition.

$ doas mkfs.ext4 $partition

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. This is also a good thing though, since it allows us to make adjustments to its configuration depending on which revision of the A20 OLinuXino Lime2 we have.

All the following commands assume that you are running on an Alpine Linux system. If you are not using Alpine Linux, or if you want to just do this inside of a chroot to not install the dependencies on your system, you can use a script like alpine-chroot-install to run the following commands in a chroot.

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.

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.

To roughly summarize what I understood:

Besides the board revision, the board is available with or without eMMC storage. The configuration file for the board without eMMC is at "configs/A20-OLinuXino-Lime2_defconfig", the one with eMMC is "configs/A20-OLinuXino-Lime2-eMMC_defconfig", so you should make your additions in the appropriate file for your board.

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".

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

Finally, we run 'make' to build u-boot:

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=$sdcard 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.

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:

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.

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.

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 unicorn-spam@regrow.earth (without the -spam)!