Hubris is a microcontroller operating environment designed for deeply-embedded systems with reliability requirements. Its design was initially proposed in RFD41, but has evolved considerably since then.
Developer documentation is in Asciidoc in the doc/
directory. It gets rendered
via GitHub pages, and is available at https://oxidecomputer.github.io/hubris .
The repo is laid out as follows.
-
app/
is where the top-level binary crates for applications live, e.g.app/gimlet
contains the firmware crate for Gimlet. Generally speaking, if you want to build an image for something, look here. -
build/
contains the build system and supporting crates. -
doc/
contains developer documentation. -
drv/
contains drivers, a mix of simple driver lib crates and fully-fledged server bin crates. Current convention is thatdrv/SYSTEM-DEVICE
is the driver forDEVICE
onSYSTEM
(whereSYSTEM
is usually an SoC name), whereasdrv/SYSTEM-DEVICE-server
is the server bin crate. -
lib/
contains assorted utility libraries we've written. If you need to make a reusable crate that doesn't fit into one of the other directories, it probably belongs here. -
stage0/
is the bootloader/hypovisor, primarily for LPC55. -
support/
contains some interface and programming support files, like fake certificates and programmer firmware images. -
sys/
contains the "system" bits of Hubris, namely the kernel (sys/kern
), the shared crate defining the ABI (sys/abi
), and the user library used by tasks (sys/userlib
). -
task/
contains reusable tasks that aren't drivers. The distinction between things that live intask
vs indrv/something-server
is fuzzy. Use your judgement. -
test/
contains the test framework and binary crates for building it for various boards.
We currently support Linux and Windows as first-tier platforms. The build probably also works on Illumos and Mac; if anyone would like to step up to maintain support and a continuous build for those architectures, we'd love the help.
To submit changes for review, push them to a new branch in this repo (not a separate GitHub fork) and submit a pull request to merge that branch into master. (Pull requests from separate GitHub forks do not currently work. First, the pre-integration checks do not get run automatically in forks, so you'd have to enable Actions in your fork. More importantly, these checks require configuration of a repository secret in order to clone private submodules, so they'll fail when run from a fork.)
You will need:
-
A
rustup
-based toolchain install.rustup
will take care of automatically installing our pinned toolchain version, and the cross-compilation targets, when you first try to build. -
openocd
(ideally 0.11) or (if using the LPC55)pyocd
(0.27 or later). Note that the 0.10 release of OpenOCD predates the STLink v3. People are using various post-0.10, pre-0.11 builds provided by system package managers, with some success, but if your system isn't packaging 0.11 yet, pester them. If you're going to use Homebrew on macOS to install OpenOCD, you need to usebrew install --head openocd
to build the tip of the main branch rather than using the latest binary release. -
The appropriate Rust toolchain target installed (note: this should happen automatically):
rustup target add thumbv7em-none-eabihf
(for the STM32)rustup target add thumbv8m.main-none-eabihf
(for the LPC55)
-
libusb, typically found from your system's package manager as
libusb-1.0.0
or similar. -
libfdti1, found as
libftdi1-dev
or similar. -
arm-none-eabi-objcopy
andarm-none-eabi-gdb
, typically from your system's package manager using package names likearm-none-eabi-binutils
andarm-none-eabi-gdb
. macOS users can runbrew install --cask gcc-arm-embedded
to install the official ARM binaries. -
The Hubris debugger, Humility:
cargo install --git ssh://git@github.com/oxidecomputer/humility.git
If you're on Windows, you can get set up by doing this:
See here for getting the source of openocd
or get unofficial binaries. Alternatively, you can install via
with chocolatey:
> choco install openocd
You can easily install openocd
with scoop:
> scoop bucket add extras
> scoop install openocd
NOTE: openocd
installed via scoop
has proven problematic for some users. If
you experience problems, try installing via choco
or from source (see above).
You'll probably need to install this driver.
If your terminal doesn't support serial connections, you'll want to also use PuTTY; this guide does a good job of explaining how.
Finally, the instructions below invoke shell scripts. They're very small, and so for now you can run the commands manually, yourself. We may improve this in the future.
We do not use cargo build
or cargo run
directly because they are too
inflexible for our purposes. We have a complex multi-architecture build, which
is a bit beyond them.
Instead, the repo includes a Cargo extension called xtask
that namespaces our
custom build commands.
cargo xtask dist TOMLFILE
builds a distribution image for the application described by the TOML file.cargo xtask dist app/demo-stm32f4-discovery/app.toml
- stm32f4-discoverycargo xtask dist app/demo-stm32f4-discovery/app-f3.toml
- stm32f3-discoverycargo xtask dist app/lpc55xpresso/app.toml
- lpcxpresso55s69cargo xtask dist app/demo-stm32h7-nucleo/app-h743.toml
- nucleo-ih743zi2cargo xtask dist app/demo-stm32h7-nucleo/app-h753.toml
- nucleo-ih753zicargo xtask dist app/demo-stm32h7-nucleo/app-h7b3.toml
- stm32h7b3i-dkcargo xtask dist app/gemini-bu/app.toml
- Gemini bringup board
cargo xtask build
from within a task or kernel directory builds that one component in isolation. The result won't actually run, because it's missing important system context, but this provides a cheaper way to do incremental builds during development. See theIterating
section below.
Note: Because the Hubris repository contains submodules, before building, you
must update submodules, either via git clone --recursive
, or
git submodule update --init
or similar.
Details depend on your target board. Here are some common evaluation boards we support.
See the Gemini Bringup Getting Started docs.
Connect board via USB.
From one terminal, launch openocd
with the corresponding config file:
$ openocd -f app/demo-stm32f4-discovery/openocd.cfg # STM32F4 Discovery boards OR
$ openocd -f app/demo-stm32f4-discovery/openocd-f3.cfg # STM32F3 Discovery boards
From another terminal:
$ cargo xtask gdb app/demo-stm32f4-discovery/app.toml openocd.gdb # STM32F4 Discovery boards OR
$ cargo xtask gdb app/demo-stm32f4-discovery/app-f3.toml openocd-f3.gdb # STM32F3 Discovery boards
If this works, you'll see gdb
mumble some things about flashing, openocd
will scroll by some updates, and you will eventually be unceremoniously
deposited at a (gdb)
prompt halted on the first instruction. Type c
/
continue
to run.
Note that for the STM32F3 Discovery, SB10 must be soldered closed for ITM to work! This solder bridge defaults to being open, which leaves SWO disconnected. See the STM32F3 Discovery User Manual (UM1570) for schematic and details.
To use the LPCXpresso55S69, you will need pyOCD, version 0.27.0 or later.
The LPCXpresso55S69 is somewhat of a mess because the built-on on-chip debugger, LPC-Link2, does not correctly support SWO/SWV
If you have the stock LPC-Link2, it will report itself this way via pyocd list
:
$ pyocd list
# Probe Unique ID
-----------------------------------------------------------------
0 NXP Semiconductors LPC-LINK2 CMSIS-DAP V5.361 JSAQCQIQ
It's also possible that you have the Segger J-Link firmware -- firmware
that will make its odious presence known by prompting for you to accept license
terms whenever running pyocd list
!
$ pyocd list
# Probe Unique ID
-----------------------------------------------------------------------------
0 Segger J-Link LPCXpresso V2 compiled Apr 4 2019 16:54:03 726424936
In either of these cases you must -- as a one-time step -- install new firmware on the LPC-Link2. The new firmware is a build of the (open source) DAPLink, which we affectionally call RickLink after the engineer who managed to get it all built -- no small feat!
There are two files that you will need, both contained in the Hubris repository:
You will additionally need the LPCScrypt program from NXP.
Here are the steps to install RickLink:
-
Install the DFU jumper. This can be found next to the SWD header on the left side of the board; it is labelled "DFU".
-
Run
scripts/boot_lpcscrypt
from the installed LPCScrypt software:
$ /usr/local/lpcscrypt/scripts/boot_lpcscrypt
Looking for DFU devices with VID 1fc9 PID 000c ...
dfu-util -d 1fc9:000c -c 1 -i 0 -t 2048 -R -D /usr/local/lpcscrypt/scripts/../bin/LPCScrypt_228.bin.hdr
Booted LPCScrypt target (1fc9:000c) with /usr/local/lpcscrypt/scripts/../bin/LPCScrypt_228.bin.hdr
$
- Run
lpcscrypt clockslow
:
$ /usr/local/lpcscrypt/bin/lpcscrypt clockslow
$
- Run
lpcscrypt program +c <path-to-lpc4322_bl_crc.bin> BankA
:
$ /usr/local/lpcscrypt/bin/lpcscrypt program +c ~/hubris/support/lpc4322_bl_crc.bin BankA
..
Programmed 57344 bytes to 0x1a000000 in 0.827s (67.717KB/sec)
$
-
Assuming it is successful, remove the DFU jumper and disconnect/reconnect USB
-
There should now be a USB mass storage device named
MAINTENANCE
# fdisk -l
Disk /dev/nvme0n1: 477 GiB, 512110190592 bytes, 1000215216 sectors
Disk model: Micron 2200S NVMe 512GB
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: A8653F99-39AB-4F67-A9C9-524A2864856E
Device Start End Sectors Size Type
/dev/nvme0n1p1 2048 1050623 1048576 512M EFI System
/dev/nvme0n1p2 1050624 967393279 966342656 460.8G Linux filesystem
/dev/nvme0n1p3 967393280 1000214527 32821248 15.7G Linux swap
Disk /dev/sda: 64.1 MiB, 67174400 bytes, 131200 sectors
Disk model: VFS
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
# mount /dev/sda /mnt
# ls /mnt
DETAILS.TXT PRODINFO.HTM
# cat /mnt/DETAILS.TXT
# DAPLink Firmware - see https://mbed.com/daplink
Unique ID: 02360b000d96e4fc00000000000000000000000097969905
HIC ID: 97969905
Auto Reset: 1
Automation allowed: 1
Overflow detection: 1
Daplink Mode: Interface
Interface Version: 0254
Bootloader Version: 0254
Git SHA: f499eb6ec4a847a2b78831fe1acc856fd8eb2f28
Local Mods: 1
USB Interfaces: MSD, CDC, HID, WebUSB
Bootloader CRC: 0x09974fb3
Interface CRC: 0x7174ab4c
Remount count: 0
URL: https://os.mbed.com/platforms/LPCXpresso55S69/
- Copy
lpc4322_lpc55s69xpresso_if_rla_swo_hacks.bin
to the USB drive
$ sudo cp ~/hubris/support/lpc4322_lpc55s69xpresso_if_rla_swo_hacks.bin /mnt
$
- Unmount (or otherwise sync) the USB drive:
# umount /mnt
#
- Unplug and replug the USB cable.
Verify that you are on the new firmware by running pyocd list
:
$ pyocd list
# Probe Unique ID
-------------------------------------------------------------------------------------
0 LPCXpresso55S69 [lpc55s69] 02360b000d96e4fc00000000000000000000000097969905
Once RickLink is running:
- Terminal 1:
pyocd gdbserver -t lpc55s69
- Terminal 2:
telnet localhost 4444
(semihosting output) - Terminal 3:
cargo xtask gdb app/lpc55xpresso/app.toml openocd.gdb
Note that the RickLink running on the LPCXpresso55S69 can also be used as the debugger for the LPC55S28 on the Gemini carrier board. To do this, first, follow all of the instructions above to get RickLink onto your LPCXpresso55S69. Then:
-
Using a soldering iron, solder a two-pin header on J5. J5 can be be found to the left of P1 and below the "Debugger" jumper (J3).
-
Put a jumper on the new header
-
Move the "Debugger" jumper (J3) to "Ext".
-
Use a SWD cable (10-pin 2x5 1.27mm pitch cable) to connect the SWD on the LPCXpresso55S69 to the SWD underneath the carrier board on Gemini (J202)
(To allow your RickLink to once again debug its local LPC55S69, remove the jumper on J5 and move J3 to "Loc".)
- Terminal 1:
cd app/demo-stm32h7-nucleo; openocd
- Terminal 2:
cargo xtask gdb app/demo-stm32h7-nucleo/app-h743.toml openocd.gdb
- Terminal 1:
cd app/demo-stm32h7-nucleo; openocd
- Terminal 2:
cargo xtask gdb app/demo-stm32h7-nucleo/app-h753.toml openocd.gdb
- Terminal 1:
cd app/demo-stm32h7-nucleo; openocd
- Terminal 2:
cargo xtask gdb app/demo-stm32h7-nucleo/app-h7b3.toml openocd.gdb
If multiple probes are attached, tools may struggle to find the right one at
the right time. In particular, OpenOCD will pick the first one that it finds;
to force OpenOCD to pick a particular probe,
you can ascertain the serial number of the probe (e.g., from humility probe
)
and then specify that serial number in the corresponding openocd.cfg
by
adding, e.g.:
interface hla
hla_serial 271828182845904523536028
(Where 271828182845904523536028
is the serial number of the probe.)
It is common that debugging dongles, and development boards with embedded debug hardware like the Nucleo series, are delivered with older firmware.
You will not be able to use Humilty with outdated ST-Link firmware.
Follow this "ST-LINK firmware upgrade" link to find software and instructions necessary to install current firmware.
An image within a Hubris archive can be flashed directly onto a target board
by running cargo xtask flash
and specifying the appropriate
TOML file. This will run cargo xtask dist
and then execute the appropriate
command (either OpenOCD or pyOCD) to flash the image; the exact invocation
depends on the board:
- LPCXpresso55S69:
cargo xtask flash app/lpc55xpresso/app.toml
- STM32F4 Discovery board:
cargo xtask flash app/demo-stm32f4-discovery/app.toml
- ST Nucleo-H743ZI2 board:
cargo xtask flash app/demo-stm32h7-nucleo/app-h743.toml
- ST Nucleo-H753ZI board:
cargo xtask flash app/demo-stm32h7-nucleo/app-h753.toml
- ST STM32H7B3I-DK board:
cargo xtask flash app/demo-stm32h7-nucleo/app-h7b3.toml
- Gemini bringup board:
cargo xtask flash app/gemini-bu/app.toml
The Hubris debugger, Humility, is run in situ by specifying an archive on a directly connected board, or postmortem by specifying a dump. As a convenience for development, Humility can also be run in situ by specifying the appropriate TOML, e.g. on a machine with an STM32F4 Discovery board directly attached:
$ cargo xtask humility app/demo-stm32f4-discovery/app.toml tasks
Finished dev [optimized + debuginfo] target(s) in 0.17s
Running `target/debug/xtask humility demo/app.toml tasks`
humility: attached via ST-Link
ID ADDR TASK GEN STATE
0 20000108 jefe 0 Healthy(InRecv(None))
1 20000178 rcc_driver 0 Healthy(InRecv(None))
2 200001e8 usart_driver 0 Healthy(InRecv(None))
3 20000258 user_leds 0 Healthy(Runnable) <-
4 200002c8 ping 48 Healthy(Runnable)
5 20000338 pong 0 Healthy(InRecv(None))
6 200003a8 idle 0 Healthy(Runnable)
The Hubris kernel is tested with a dedicated test image that includes a test
runner, assistant and test suite. The test image emits its results via ITM.
While these results can be interpreted manually, humility test
automates
this. humility test
itself is most easily run via cargo xtask test
, which
runs the equivalent of cargo xtask dist
, cargo xtask flash
and cargo xtask humility test
. The exact invocation depends on the board:
- LPCXpresso55S69:
cargo xtask test test test/tests-lpc55/app.toml
- STM32F3 Discovery board:
cargo xtask test test/tests-stm32fx/app-f3.toml
Note: for this board, SB10 must be soldered closed for ITM to work - STM32F4 Discovery board:
cargo xtask test test/tests-stm32fx/app.toml
- ST Nucleo-H743ZI2 board:
cargo xtask test test/tests-stm32h7/app-h743.toml
- ST Nucleo-H753ZI board:
cargo xtask test test/tests-stm32h7/app-h753.toml
- ST STM32H7B3I-DK board:
cargo xtask test test/tests-stm32h7/app-h7b3.toml
See the documentation for humility test
for details
on test results.
Output from tests is captured by humility test
; sys_log!()
calls to
tests can be added and then captured in a humility test
dump. To capture
a dump from tests that are otherwise passing, use cargo xtask humility
directly and pass the -d
flag, e.g.:
$ cargo xtask humility test/tests-stm32fx/app.toml -- test -d
...
humility: attached via ST-Link
humility: TPIU sync packet found at offset 1
humility: ITM synchronization packet found at offset 12
humility: expecting 22 cases
humility: running test_send ... ok
...
humility: running test_timer_notify ... ok
humility: running test_timer_notify_past ... ok
humility: tests completed: pass
humility: test output dumped to hubris.testout.2
if one needs to both run GDB and the test suite, use cargo xtask gdb
with the test image's TOML and the appropriate GDB file, and then place
breakpoints at the test of interest.
To create your own task, the easiest method is:
- Copy
task-template
to a new name. - Edit its
Cargo.toml
with your name and a new package name. - Add it to the list of workspace members in the root
Cargo.toml
.
You can now run cargo xtask build
in the task's directory to run a standalone
build. (See below for details.)
To actually test the task, you need to add it to a system image by editing an
app.toml
file. A typical entry for a small task that uses no memory-mapped
peripherals would read
[tasks.name_for_task_in_this_image]
path = "../my-task-directory"
name = "my-task-target-name"
priority = 1
requires = {flash = 1024, ram = 1024}
start = true
Because a full image build can take 10 seconds or more, depending on what you've changed, when you're iterating on a task or kernel you'll probably want to build it outside the context of an image. We have a thing called a "standalone build" for this.
For instance, to run a standalone build of task-ping
, run:
$ cd task-ping
$ cargo xtask build
This magic happens in three parts:
- Packages that support standalone build have a
package.metadata.build.target
key giving the default target architecture for standalone builds. - Our
build.rs
files that receive information from thedist
xtask will do reasonable defaulty things if that information is missing, e.g. in this case. - By convention, we set a default feature
"standalone"
for standalone builds, and switch it off in theapp.toml
used by thepackage
xtask. You can use this feature to conditionally compile stuff.
Note: most tasks pick up their build.rs
behavior implicitly by depending on
userlib
. You generally do not need a build.rs
in your task unless you need
to detect compiler/architecture features or depend on board rev.