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:
- Suppressing log output.
- 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
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:
-
In
config.txt
make sure to setdisable_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. -
The kernel boot parameters must be adjusted to suppress log output. The
parameters are set in one long line in the filecmdline.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:
(Linux Kernel 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
- Rotating the splash screen to support upright screens. This can be done
with a firmware variable. - Add a requirement to firmware upgrades that their
MIX_ENV
variable
matches what is stored inmix_env
.