Booting the Rasperry Pi with a splash image when using Nerves

By default when you boot an RPi running Nerves it will output a lot of log
messages to the screen. Super useful when you’re in development, but not so
pretty when used for products that have a screen attached to the RPi. We have
one such product that we might migrate to Nerves, so we decided to figure out
how to put a fancy boot splash on the RPi.

This requires two things:

  1. Suppressing log output.
  2. Outputting an image to the screen.

I will start by explaining the general steps, and then follow up with a few
tips to enabling this only in production (so we keep all the useful log
messages for development devices). I’ve also made a branch off the official
Nerves RPi3 system with the changes described. The changes required are in
the two most recent commits
here
. Go there if you need a TL;DR :slight_smile:

Note that this requires customizing the official Nerves RPi system - if
you’re not sure what that means, check this out.

Suppressing log output

Both the RPi bootloader, the Linux kernel and our Nerves application will
output stuff (log messages and images) on screen by default. This can be
disabled by setting some options in the files config.txt and cmdline.txt,
which are generated by Buildroot. The settings are:

  1. In config.txt make sure to set disable_splash=1. This will disable the
    boot rainbow. This is already set by default on the RPi3, so we actually
    don’t need to do anything here.

  2. The kernel boot parameters must be adjusted to suppress log output. The
    parameters are set in one long line in the file cmdline.txt. There’s two
    ways to suppress the logout: a) quiet and redirect the console output or b)
    disable the framebuffer console entirely.

a) Quiet and redirect console output

This is the less draconian approach. Instead of printing to tty1, we’ll
direct all output to tty3. This means the output will still be available,
but it won’t be on-screen by default. You can access it by switching to tty3
by clicking CTRL+ALT+F3. It’s not a perfect solution though. For some reason,
some of the output might still be printed on screen. Therefore, we also
minimize the amount of kernel output. To achieve this, ensure the following
options are set in cmdline.txt:

console=tty3 quiet loglevel=0 logo.nologo vt.global_cursor_default=0

The console= option is already specified as tty1 by default, so that has
to be updated. The other keys are new. Their meanings are:

  • quiet: Disables most log messages.
  • loglevel=0: This sets the loglevel of the kernel. Setting it to 0 means it
    only prints emergency level warnings.
  • logo.nologo: This disables printing of the Raspberry Pi logos.
  • vt.global_cursor_default=0: This disables the blinking cursor.

(A good reference to the kernel boot parameters can be found here:
(http://redsymbol.net/linux-kernel-boot-parameters/)

We also need to make sure that the log output from our Nerves application goes
to tty3. This is done by setting -c tty3 in the erlinit.config file (by
default it is set to tty1).

All these settings will generally result in a silent boot, but I have read
that the Linux kernel may still print a few log messages to the screen. If
this is not acceptable, we must disable the framebuffer console instead.

b) Disable the framebuffer console

This is the more draconian method. Disabling the framebuffer console will
quelch all output, but it also means you can no longer interact with the
device using screen and keyboard. This is actually an added bonus for us, as
we don’t want that to be possible on production devices. So even though the
less draconian method worked for us, we chose to disable the framebuffer for
this reason.

Disabling the framebuffer console is done by setting the following to
cmdline.txt: fbcon=map:2. This will allocate the console to an
unavailable framebuffer (the RPi only has one framebuffer), effectively
disabling it. Thus, it replaces all the other options suggested above, and
you don’t need to update erlinit.config either.

Outputting an image to the screen

Buildroot has a package for a program called fbv, which can be used for
outputting an image to the screen. It does this by writing to the framebuffer
(I had no idea what that meant, but there is a nice explanation
here). It’s
not included by default by Nerves, so it must be added to the
nerves_defconfig file. You can do this through the Buildroot menuconfig
interface, or by adding the line BR2_PACKAGE_FBV=y to the file.

Once fbv has been added to the system, it must be invoked at boot. We can
do this by telling erlinit to invoke a script before starting the Erlang
VM. This is done by adding the following to the erlinit.config file:
--pre-run-exec "sh /splash.sh". The file splash.sh must be added to
the rootfs_overlay directory, and is a simple one-liner:

#! /bin/sh

# The call to fbv must be backgrounded, otherwise it blocks which means
# the Erlang VM won't boot
fbv /splash.png &

This call to fbv will render the image /splash.png to the framebuffer.
Thus, add an image with that name to the rootfs_overlay directory, and
you’re done.

Enabling it only in production

To enable this only in production, we need to have two different cmdline.txt files
available, one for production and one for development. Thus, you will need two files,
cmdline-dev.txt and cmdline-prod.txt. cmdline-dev.txt should be a copy of the
current cmdline.txt, while cmdline-prod.txt should be modified to either suppress
the kernel logs or disable the framebuffer console, as described above.

To ensure the right cmdline.txt file is used depending on the value of
MIX_ENV, we’ll use that variable in fwup.conf to select the correct file
when generating firmware. Add a fallback definition of MIX_ENV and then
update the host path for the file:

...
#
# Firmware metadata
#

define(MIX_ENV, "${MIX_ENV:-dev}") # Add this line
...
file-resource cmdline.txt {
    host-path = "${NERVES_SYSTEM}/images/cmdline-${MIX_ENV}.txt" # Update this line
}
...

Next, we’ll also add a firmware variable with the content of MIX_ENV.
This will be used to check whether the splash screen is to be rendered or not.
Add a line in the complete task in fwup.conf:

...
task complete {
   ...
   uboot_setenv(uboot-env, "nerves_fw_devpath", ${NERVES_FW_DEVPATH})
   uboot_setenv(uboot-env, "mix_env", ${MIX_ENV}) # Add this line
   uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
   ...
}
...

Then update the splash.sh script to check the mix_env variable:

#! /bin/sh

if fw_printenv | grep -q "mix_env=prod"; then
    # The call to fbv must be backgrounded, otherwise it blocks which means
    # the Erlang VM won't boot
    fbv /splash.png &
fi

And you’re done! If in doubt, you can see all the changes you need to make on
the two most recent commits here.

Extras

Of course, we’re never really done. One could also consider

  1. Rotating the splash screen to support upright screens. This can be done
    with a firmware variable.
  2. Add a requirement to firmware upgrades that their MIX_ENV variable
    matches what is stored in mix_env.
8 Likes