This guide covers how to port Tock to a new platform.
It is a work in progress. Comments and pull requests are appreciated!
- Crate Details
- Adding a Platform to Tock Repository
At a high level, to port Tock to a new platform you will need to create a new
"board" as a crate, as well as potentially add additional "chip" and "arch"
crates. The board crate specifies the exact resources available on a hardware
platform by stitching capsules together with the chip crates (e.g. assigning
pins, setting baud rates, allocating hardware peripherals etc.). The chip crate
implements the peripheral drivers (e.g. UART, GPIO, alarms, etc.) for a specific
microcontroller by implementing the traits found in
kernel/src/hil. If your
platform uses a microncontroller already supported by Tock then you can use the
existing chip crate. The arch crate implements the low-level code for a specific
hardware architecture (e.g. what happens when the chip first boots and how
system calls are implemented).
This section includes more details on what is required to implement each type of crate for a new hardware platform.
Tock currently supports the ARM Cortex-M0, Cortex-M3, and Cortex M4, and the riscv32imac architectures. There is not much architecture-specific code in Tock, the list is pretty much:
- Syscall entry/exit
- Interrupt configuration
- Top-half interrupt handlers
- MPU configuration (if appropriate)
- Power management configuration (if appropriate)
It would likely be fairly easy to port Tock to another ARM Cortex M (specifically the M0+, M23, M4F, or M7) or another riscv32 variant. It will probably be more work to port Tock to other architectures. While we aim to be architecture agnostic, this has only been tested on a small number of architectures.
If you are interested in porting Tock to a new architecture, it's likely best to reach out to us via email or Slack before digging in too deep.
chip crate is specific to a particular microcontroller, but should attempt
to be general towards a family of microcontrollers. For example, support for the
nRF58230 microcontrollers is shared in the
chips/nrf5x crates. This helps reduce duplicated code and simplifies adding
new specific microcontrollers.
chip crate contains microcontroller-specific implementations of the
interfaces defined in
Chips have a lot of features and Tock supports a large number of interfaces to express them. Build up the implementation of a new chip incrementally. Get reset and initialization code working. Set it up to run on the chip's default clock and add a GPIO interface. That's a good point to put together a minimal board that uses the chip and validate with an end-to-end userland application that uses GPIOs.
Once you have something small like GPIOs working, it's a great time to open a pull request to Tock. This lets others know about your efforts with this chip and can hopefully attract additional support. It also is a chance to get some feedback from the Tock core team before you have written too much code.
Moving forward, chips tend to break down into reasonable units of work.
Implement something like
kernel::hil::UART for your chip, then submit a pull
request. Pick a new peripheral and repeat!
Historically, Tock chips defined peripherals as
static mut global variables, which made
them easy to access but encouraged use of unsafe code and prevented boards from
instantiating only the set of peripherals they needed. Now, peripherals are instantiated
at runtime in
main.rs, which resolves these issues. To prevent each board from having
to instantiate peripherals individually, chips should provide a
struct that defines and creates all peripherals available for the chip in Tock. This will be
used by upstream boards using the chip, without forcing the overhead and code size of all peripherals
on more minimal out-of-tree boards.
board crate, in
boards/src, is specific to a physical hardware platform.
The board file essentially configures the kernel to support the specific
hardware setup. This includes instantiating drivers for sensors, mapping
communication buses to those sensors, configuring GPIO pins, etc.
Tock is leveraging "components" for setting up board crates. Components are contained structs that include all of the setup code for a particular driver, and only require boards to pass in the specific options that are unique to the particular platform. For example:
let ambient_light = AmbientLightComponent::new(board_kernel, mux_i2c, mux_alarm) .finalize(components::isl29035_component_helper!(sam4l::ast::Ast));
instantiates the component for an ambient light sensor. Board initiation should
be largely done using components, but not all components have been created yet,
so board files are generally a mix of components and verbose driver
instantiation. The best bet is to start from an existing board's
and adapt it. Initially, you will likely want to delete most of the capsules and
add them slowly as you get things working.
Warning: Components are singletons, that is they may not be instantiated multiple times. Components should only be instantiated in the reset handler to avoid any multiple instantiations.
In addition to kernel code, boards also require some support files. These specify metadata such as the board name, how to load code onto the board, and anything special that userland applications may need for this board.
Each board must author a custom routine to handle
machinery is handled by the Tock kernel, but the board author must provide
some minimalist access to hardware interfaces, specifically LEDs and/or UART.
As a first step, it is simplest to just get LED-based
panic! working. Have
panic! handler set up a prominent LED and then call
If UART is available, the kernel is capable of printing a lot of very helpful
additional debugging information. However, as we are in a
it's important to strip this down to a minimalist implementation. In particular,
the supplied UART must be synchronous (note that this in contrast to the rest of
the kernel UART interfaces, which are all asynchronous). Usually implementing a
Writer that simply writes one byte at a time directly to the UART
is easiest/best. It is not important that
panic! UART writer be efficient.
You can then replace the call to
with a call to
For largely historical reasons, panic implementations for all boards live in
a file named
io.rs adjacent to the board's
Board Cargo.toml, build.rs
Every board crate must author a top-level manifest,
Cargo.toml. In general,
you can probably simply copy this from another board, modifying the board name
and author(s) as appropriate. Note that Tock also includes a build script,
build.rs, that you should also copy. The build script simply adds a
dependency on the kernel layout.
There is a Makefile in the root of every board crate, at a minimum, the board Makefile must include:
# Makefile for building the tock kernel for the Hail platform TARGET=thumbv7em-none-eabi # Target triple PLATFORM=hail # Board name here include ../Makefile.common # ../ assumes board lives in $(TOCK)/boards/<board>
boards/Makefile.common that drives most of the build system.
In general, you should not need to
dig into this Makefile -- if something doesn't seem to be working, hop on slack
Getting the built kernel onto a board
In addition to building the kernel, the board Makefile should include rules for getting code onto the board. This will naturally be fairly board-specific, but Tock does have two targets normally supplied:
- program: For "plug-'n-plug" loading. Usually these are boards with a
bootloader or some other support IC. The expectation is that during normal
operation, a user could simply plug in a board and type
make programto load code.
- flash: For "more direct" loading. Usually this means that a JTAG or some equivalent interface is being used. Often it implies that external hardware is required, though some of the development kit boards have an integrated JTAG on-board, so external hardware is not a hard and fast rule.
- install: This should be an alias to either
flash, whichever is the preferred approach for this board.
If you don't support program or flash, you should define an empty rule that explains how to program the board:
.PHONY: program echo "To program, run SPEICAL_COMMAND" exit 1
Every board must have a
README.md file included in the top level of the crate.
This file must:
- Provide links to information about the platform and how to purchase/acquire the platform. If there are different versions of the platform the version used in testing should be clearly specified.
- Include an overview on how to program the hardware, including any additional dependencies that are required.
Ideally, Tockloader will support loading apps on to your board (perhaps with some flags set to specific values). If that is not the case, please create an issue on the Tockloader repo so we can update the tool to support loading code onto your board.
- Make sure you are careful when setting up the board
main.rsfile. In particular, it is important to ensure that all of the required
set_clientfunctions for capsules are called so that callbacks are not lost. Forgetting these often results in the platform looking like it doesn't do anything.
Adding a Platform to Tock Repository
After creating a new platform, we would be thrilled to have it included in mainline Tock. However, Tock has a few guidelines for the minimum requirements of a board that is merged into the main Tock repository:
- The hardware must be widely available. Generally that means the hardware platform can be purchased online.
- The port of Tock to the platform must include at least:
Consolesupport so that
- Timer support.
- GPIO support with interrupt functionality.
- The contributor must be willing to maintain the platform, at least initially, and help test the platform for future releases.
With these requirements met we should be able to merge the platform into Tock relatively quickly. In the pull request to add the platform, you should add this checklist:
### New Platform Checklist - [ ] Hardware is widely available. - [ ] I can support the platform, which includes release testing for the platform, at least initially. - Basic features are implemented: - [ ] `Console`, including `debug!()` and userspace `printf()`. - [ ] Timers. - [ ] GPIO with interrupts.