Linux distribution for running escape boxes on Raspberry Pi with Rust
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
Tommy van der Vorst 739001d587
Document files that are read from the boot partition on boot
1 month ago
src Set more recent base date 1 month ago
.dockerignore Remove Rust parts, these will be separate repositories 1 year ago
.gitignore Update kernel to 5.4.42 and attempt to add bluetooth 9 months ago
README.md Document files that are read from the boot partition on boot 1 month ago
make-initrd.sh Move large ffmpeg binary out of initramfs 6 months ago
start-emulator.sh Update kernel to 5.4.42 and attempt to add bluetooth 9 months ago
update-firmware.sh Update update script 1 month ago

README.md

Box-Linux

A Linux distribution for running Rust-based escape box experiences. It is bult to drive drive Raspberry hardware (GPIO, SPI, I2C) while booting as fast as possible.

Building a bootable image

Boot process

The application can be run using a very minimal Linux-based image. The Raspberry Pi boots off SD cards. In order to boot, the SD card must contain a first partition that is VFAT formatted. On this partition, the Raspberry bootloader reads the config.txt file for boot options. This file specifies the kernel to load, which initramfs to load, what device tree options to enable, and the command line for the kernel. Our boot image includes a Linux kernel and device tree overlay files borrowed from TinyCore Linux piCore.

Our image boots as follows:

  1. The config.txt file tells the Raspberry to load the included Linux kernel, uncompressing initramfs.cpgz to a RAM-based file system right after the kernel’s memory.
  2. The cmdline.txt tells the kernel to mount the RAM filesystem as root filesystem
  3. The kernel calls the /init script inside the initramfs.
  4. The init script creates the basic folder structure and inistalls busybox
  5. The init script inserts the kernel modules necessary for SPI and I2C
  6. The init script starts mdev, the Busybox program that creates the /dev/* files
  7. The init script calls the /boot/box executable (i.e. in the root of the boot volume)

Building the initramfs and boot image

The build-initrd.sh script builds the initramfs.cpgz file from the initrd folder, placing the built Rust binary (from target/arm-linux-unknown-musleabi/release/box) into the initramfs under /box’.

The script places the built initramfs.cpgz in the build/boot folder. It will also copy all files from the boot folder there.

In order to create a bootable SD card, simply format it as an MBR-based disk with (at least) a single VFAT (“MS-DOS”) partition (as first partition). Then, copy all contents of the build/boot directory to the root of the VFAT partition on the SD card.

Updating the image

Note that the current kernel and modules were taken from piCore, not the Raspberry Pi repository linked above.

Emulation

The resulting initramfs image can be tested in a QEMU emulator.

First, install the QEMU ARM system using apt install qemu-system-arm. Then, use ./start-emulator.sh to start an ARM-based emulated machine based on the initramfs in ./build/boot/initramfs.cpgz (this should be built first using ./make-initrd.sh).

Functionality

Config files read during boot

  • ssh.conf: when present, enables SSH server
  • dropbear_ecdsa_host_key: when present, is used as host key for SSH. Otherwise a host key is generated on each boot
  • hostapd.conf: when present, hostapd is started with the configuration file (Wi-Fi access point functionality)
  • wpa_supplicant/.conf: when present, the device will try to connect to Wi-Fi
  • box: will be executed during init; after devices have been initialized but before networking has started.

Storage

The SD card’s contents are mounted as /boot and read-only by default. To mount as read-wite:

mount -o remount,rw /boot

It is strongly advised to keep /boot mounted read-only. Many SD cards perform processing on write (i.e. to deal with bad memory blocks) and data can become corrupted if power is interrupted during this.

USB serial

When used on a Pi Zero, a USB serial interface is provided. Plug in the Pi using the ‘USB’ (not ‘PWR’) port to a computer. On macOS, look for a “/dev/cu.usbmodemXXXX” device and connect to it using screen /dev/cu.usbmodemXXXX. By default you will be provided with a root shell.

To disable (i.e. use the Pi as USB host rather than device), remove the modules or disable the relevant lines in the /init script.

Audio

Audio is available as follows:

  • Through config.txt, the Pi is configured to use pins 18 and 13 for PWM-based audio. This allows the Pi Zero to output audio even though it doesn’t have a headphone jack.
  • The init script loads sound kernel modules in the correct order
  • The ALSA utilities and configuration files are included in /usr/bin and /usr/local/share/alsa respectively in the initrd. Both are available in utils/build and can be cross-compiled using Docker (run build-using-docker.sh).
  • Run aplay file.wav from the command line to play some music!

To disable, remove the modules or disable the relevant lines in the /init script.

Wi-Fi

Station mode

The device can auto-connect to a Wi-Fi network (on Pi Zero W). To use Wi-Fi, copy wpa_supplicant.conf to wpa_supplicant.conf in the root of the SD card and add your network information. The device will then attempt to connect to a network on the list on boot.

This is implemented as follows:

  • The init script loads the kernel modules necessary for the Wi-Fi device
  • Loading the modules causes firmware files to be read from /lib/firmware/brcm
  • The init script checks if a file called wpa_supplicant.conf is present on the boot device. If this is the case:
    • wpa_supplicant is started using the wpa_supplicant.conf file on the boot partition, specifying the networks to look for
    • udhcpc is started and asked to obtain an IP address from the wlan interface
      • When udhcpc obtains an address it will call /bin/dhcp.sh which will set up the IP adress and routing

To make this work on other Raspberry devices than the Zero W, supply the necessary firmware files (can be obtained from e.g. Raspbian) and possibly add other modules that are required.

To disable Wi-Fi, simply ensure there is no wpa-supplicant.conf in the boot folder. Modules may be deleted in this case to make the image smaller if so desired.

Access point mode

The device can function as an access point. In order to enable this functionality, place a hostapd.conf file in the boot directory (an example is provided here). When this file is present it will cause the following to happen on boot:

  • A process called hostapd will be started that sets up the WiFi interface to function as an access point
  • The device itself will be assigned the IP address 192.168.2.1
  • A process called udhcpd will be started that will assign each client that connects an IP address

The access point mode will create and use a virtual interface (ap0). Simultaneously connecting to Wi-Fi in station mode is supported and will be attempted when wpa_supplicant.conf is present as well. Note however that there may be issues doing this (some people have reported that the AP and station mode must use the same channel, others report that AP will auto-switch to the station mode channel).

To query hostapd, use the hostapd_cli command:

# hostapd_cli status

Selected interface 'wlan0'
state=ENABLED
phy=phy0
freq=2412
num_sta_non_erp=0
num_sta_no_short_slot_time=0
num_sta_no_short_preamble=0
olbc=0
num_sta_ht_no_gf=0
num_sta_no_ht=0
num_sta_ht_20_mhz=0
num_sta_ht40_intolerant=0
olbc_ht=0
ht_op_mode=0x0
cac_time_seconds=0
cac_time_left_seconds=N/A
channel=1
secondary_channel=0
ieee80211n=0
ieee80211ac=0
ieee80211ax=0
beacon_int=100
dtim_period=2
supported_rates=02 04 0b 16 0c 12 18 24 30 48 60 6c
max_txpower=20
bss[0]=wlan0
bssid[0]=b8:27:eb:74:33:b4
ssid[0]=MY_NETWORK_NAME
num_sta[0]=0

WPS

To be able to connect to Wi-Fi without editing the configuration file, WPS (Wireless Protected Setup) can be used. This works as follows:

  • Push (and in some cases hold for a few seconds) the WPS button on your Wi-Fi router or access point
  • Run the command wpa_cli -iwlan0 -s /var/run/wpa_supplicant/ wps_pbc. This will start scanning and autoconnect to any network in WPS mode. This command could be triggered from your application, i.e. in response to sone user input.
  • If update_config=1 in /boot/wpa_supplicant.conf and /boot is mounted read-write, the configuration will be updated and the network will be remembered.

A typical sequence, upon user requesting WPS pairing, would be to first mount /boot rw, start wps_pbc, then periodically check if pairing has succeeded (and time-out), then mount /boot read-only again.

Other useful commands:

# Get the current Wi-Fi status:
wpa_cli -iwlan0 -s /var/run/wpa_supplicant/ status 

# Cancel the WPS pairing operation (does nothing when no pairing is being performed):
wpa_cli -iwlan0 -s /var/run/wpa_supplicant/ wps_cancel

System date and time

As the Raspberry Pi has no (battery-backed) RTC, the system date and time is set to 01-01-2020 00:00:00 on each boot. This ensures that the system date and time is at least reasonable and makes for an easy reference point.

If Wi-Fi is enabled (if wpa-supplicant.conf is present in /boot), an attempt will be made to start ntpd every five seconds. If ntpd starts it will periodically fetch and update the system date/time from nl.pool.ntp.org (please edit the NTP server name in the /init script).

Camera support

The system loads V4L modules and supports the Raspberry Pi camera.

Disabling support for the camera is possible by removing the module loading from the /init script, as well as the following lines from config.txt:

start_x=1
gpu_mem=128

Streaming video is possible using ffmpeg:

/boot/bin/ffmpeg -i /dev/video0 -s 320x240 -r 1 -f mpegts udp://224.1.1.1:1234?pkt_size=1316

Video encoding with libx264 does not appear to work for some reason (“Illegal instruction”). Additionally this requires gpu_mem=16 to be set in config.txt.

Set root password

To set the root password, create a file root_password.conf in the boot directory with the root password; it will be set on each boot.

SSH server

First, set a root password. Then, create an empty file “ssh.conf” in the boot directory. This will cause the SSH server to start on boot (currently, generates host keys on each boot, so you may run into complaints from your SSH client saying that you are connecting to a different host after each reboot).

To start an SSH server from the command line:

passwd # change root user password first
mkdir /etc/dropbear
mkdir -p /home/root
dropbear -R -F -E

Other installed programs are dbclient (SSH client), dropbearkey (use to generate keys automatically), scp (enables the use of scp, both client and server).

Randomness

Some functions/programs require randomness (‘entropy’) - e.g. Wi-Fi (wpa_supplicant) will only connect once sufficient randomness. Unfortunately, there are very few sources of randomness available on a Pi running headless (the built in hardware random number generator, while available at /dev/hwrng, is not used by the kernel!). A tool such as rngd can use the hardware RNG to seed the kernel entropy pool, but is rather difficult to compile.

The box-linux image includes the haveged daemon. While it generates weaker entropy,it does allow for very fast connection to Wi-Fi upon boot and prevents slowdowns.

Running applications

NodeJS

Static NodeJS binaries for armv6l can be downloaded here.

Building Rust applications

  1. Use a recent Debian/Ubuntu installation
  2. Install Rustup
  3. Install the required components for cross-compilation (see below)
  4. Build using cargo build (optionally select a specific architecture with the --target parameter)
  5. Verify architecture of output binary: readelf --arch-specific ./target/debug/BINARYNAME

Verification output should look as follows for ARMv6:

Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "ARM v6"
  Tag_CPU_arch: v6
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-1
  Tag_ABI_PCS_GOT_use: GOT-indirect
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_rounding: Needed
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align_needed: 8-byte
  Tag_ABI_enum_size: int
  Tag_CPU_unaligned_access: v6
  Tag_ABI_FP_16bit_format: IEEE 754

Installing cross-compilation components

ARMv6
Linux (Ubuntu/Debian)
rustup target add arm-unknown-linux-musleabi
sudo apt install binutils-arm-linux-gnueabihf
macOS
brew install arm-linux-gnueabihf-binutils
rustup target add arm-unknown-linux-musleabi

Note, on macOS, cargo build may complain it cannot find the linker. To fix, invoke as follows:

PATH=$PATH:/usr/local/bin cargo build

This builds for ARMv6 (hard float) which is necessary for the Pi Zero and 1.

ARMv7

In order to build ARMv7 (hard float), use the following:

rustup target add armv7-unknown-linux-musleabihf
sudo apt-get install gcc-multilib-arm-linux-gnueabihf

Then, change cargo config accordingly:

[build]
target = "arm-unknown-linux-gnueabihf"

Pi features

The rppal crate provides access to Raspberry Pi I/O from Rust. The manual can be found here.