A photo of Evan Pratten
Evan Pratten

Running RoboRIO firmware inside Docker

Containerized native ARMv7l emulation in 20 minutes

It has now been 11 weeks since the last time I have had access to a RoboRIO to use for debugging code, and there are limits to my simulation software. So, I really only have one choice: emulate my entire robot.

My goal is to eventually have every bit of hardware on 5024’s Darth Raider emulated, and running on my docker swarm. Conveniently, everything uses (mostly) the same CPU architecture. In this post, I will go over how to build a RoboRIO docker container.

Host system requirements

This process requires a host computer with:

Getting a system image

This is the hardest step. To get a RoboRIO docker container running, you will need:

RoboRIO Firmware

To acquire a copy of the latest RoboRIO Firmware package, you will need to install the FRC Game Tools on a Windows machine (not wine).

After installing the toolsuite, and activating it with your FRC team’s activation key (provided in Kit of Parts), you can grab the latest FRC_roboRIO_XXXX_vXX.zip file from the installation directory of the FRC Imaging Tool (This will vary depending on how, and where the Game Tools are installed).

After unzipping this file, you will find another ZIP file, and a LabVIEW FPGA file. Unzip the ZIP, and look for a file called systemimage.tar.gz. This is the RoboRIO system image. Copy it to your Ubuntu computer.

Bootstrapping

The bootstrap process is made up of a few parts:

  1. Enabling support for ARM-based docker containers
  2. Converting the RoboRIO system image to a Docker base image
  3. Building a Dockerfile with hacked auth

Enabling Docker-ARM support

Since the RoboRIO system image and libraries are compiled to run on ARMv7l hardware, they will refuse to run on an x86_64 system. This is where QEMU comes in to play. We can use QEMU as an emulation layer between out docker containers and our CPU. To get QEMU set up, we must first install support for ARM->x86 emulation by running:

sudo apt install qemu binfmt-support qemu-user-static -y

Once QEMU has been installed, we must run the registration scripts with:

docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

Converting the system image to a Docker base

We have a system image filesystem, but need Docker to view it as a Docker image.

Using my pre-built image

Feel free to skip the following step, and just use my pre-built RoboRIO base image. It is already set up with hacked auth, and is (at the time of writing) based on firmware version 2020_v10.

To use it, replace roborio:latest with ewpratten/roborio:2020_v10 in the docker-compose.yml config below.

Building your own image

Make a folder, and put both the system image, and libfakearmv7l.so files in it. This will be your “working directory”. Now, import the system image into docker with:

docker import ./systemimage.tar.gz roborio:tmp

This will build a docker base image out of the system image, and name it roborio:tmp. You can use this on it’s own, but if you want to deploy code to the container with GradleRIO, or SSH into the container, you will need to strip the NI Auth.

Stripping National Instruments Auth

By default, the RoboRIO system image comes fairly locked down. To fix this, we can “extend” our imported docker image with some configuration to allow us to remove some unknown passwords.

In the working directory, we must first create a file called common_auth. This will store our modified authentication configuration. Add the following to the file:

#
# /etc/pam.d/common-auth - authentication settings common to all services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.).  The default is to use the
# traditional Unix authentication mechanisms.

# ~~~ This file is modified for use with Docker ~~~ 

# here are the per-package modules (the "Primary" block)
# auth	[success=2 auth_err=1 default=ignore]	pam_niauth.so nullok
# auth	[success=1 default=ignore]	pam_unix.so nullok
# here's the fallback if no module succeeds
# auth	requisite			pam_deny.so
# prime the stack with a positive return value if there isn't one already;
# this avoids us returning an error just because nothing sets a success code
# since the modules above will each just jump around
auth	required			pam_permit.so
# and here are more per-package modules (the "Additional" block)

Now, we must create a Dockerfile in the same directory with the following contents:

FROM roborio:tmp

# Fixes issues with the original RoboRIO image
RUN mkdir -p /var/volatile/tmp && \
    mkdir -p /var/volatile/cache && \
    mkdir -p /var/volatile/log && \
    mkdir -p /var/run/sshd

RUN opkg update && \
    opkg install binutils-symlinks gcc-symlinks g++-symlinks libgcc-s-dev make libstdc++-dev

# Overwrite auth
COPY system/common_auth /etc/pam.d/common-auth
RUN useradd admin -ou 0 -g 0 -s /bin/bash -m
RUN usermod -aG sudo admin

# Fixes for WPILib
RUN mkdir -p /usr/local/frc/third-party/lib
RUN chmod 777 /usr/local/frc/third-party/lib

# This forces uname to report armv7l
COPY system/libfakearmv7l.so /usr/local/lib/libfakearmv7l.so
RUN chmod +x /usr/local/lib/libfakearmv7l.so && \
    mkdir -p /home/admin/.ssh && \ 
    echo "LD_PRELOAD=/usr/local/lib/libfakearmv7l.so" >> /home/admin/.ssh/environment && \
    echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config && \
    echo "PasswordAuthentication no">> /etc/ssh/sshd_config

# Put the CPU into 32bit mode, and start an SSH server
ENTRYPOINT ["setarch", "linux32", "&&", "/usr/sbin/sshd", "-D" ]

This file will cause the container to:

We can now build the final image with these commands:

docker build -f ./Dockerfile -t roborio:local .
docker rmi roborio:tmp
docker tag roborio:local roborio:latest

Running the RoboRIO container locally

We can now use docker-compose to start a fake robot network locally, and run our RoboRIO container. First, we need to make a docker-compose.yml file. In this file, add:

version: "3"

services:

  roborio:
    image: roborio:latest # Change this to "ewpratten/roborio:2020_v10" if using my pre-built image
    networks:
      robo_net:
        ipv4_address: 10.50.24.2

networks:
    robo_net:
        ipam:
            driver: default
            config:
                - subnet: 10.50.24.0/24

We can now start the RoboRIO container by running

docker-compose up

You should now be able to SSH into the RoboRIO container with:

Or even deploy code to the container! (Just make sure to set your FRC team number to 5024)