Tock Logo

Talking Tock

Talking Tock 34

Registers, Review process & Releases

This is the 34th post in a series tracking the development of Tock, a safe multi-tasking operating system for microcontrollers.

We’re presenting Tock at the Linux Foundation’s OpenIoT summit in Portland in March! Check out the event website for details and registration.

  1. Code Review Process
    1. Core Team
    2. Pull-Requests
    3. Rolling Releases
  2. Register and Bitfield Macros
  3. Pull Requests
    1. Merged
    2. Proposed

Code Review Process

As Tock supports more chips and services, changes to core interfaces or capsules will increasingly trigger bugs or integration problems. In order to allow development to continue smoothly while avoiding breaking everyone’s code, we recently agreed on a process (#736) by which changes to the main Tock happen. Of course, this process is not set in stone, and may change as problems or issues arise.

Core Team

Borrowing from how the Rust team structures development, we’ve established a core team that is responsible for sheperding the development of Tock. In practice, the core team are people with commit access to the main repository and operate by consensus. The members of the core team are:

The core team reflects people who actively contribute to Tock and is representative of the teams at the institutions who work on and rely on Tock. As the ecosystem evolves, we expect the make-up of the core team to evolve as well.

Pull-Requests

Any pull request against the master branch is reviewed by the Tock core team. Pull requests fall into one of two categories:

Whether a pull request is upkeep or significant is based not only on the magnitude of the change but also what sort of code is changed. For example, bug fixes that are considered upkeep for a non-critical capsule might be considered significant for kernel code, because the kernel code affects everything and has more potential edge cases.

Upkeep pull requests can be merged by any member of the core team. That person is responsible for the merge and backing out the merge if needed. This is the roughly process we’ve used for all pull-requests until now.

Significant pull requests require review by the entire core team. Each core team member is expected to respond within one week. There are three possible responses:

A significant pull-request can be merged after a week, provided at least two core team members accepted it and there are no outstanding requests to discuss it.

Rolling Releases

Periodic stable releases make it easier for users to install and track changes to Tock. Our intention is to release approximately every two months, at the beginning of even months. One week before the intended release date, all new pull requests are put on hold, and everyone uses/tests the software using the established testing process. Bug fixes for the release are marked as such (in the title) and applied quickly. Once the release is ready, the core team makes a branch with the release number and pull request reviews restart.

Release branches are named ‘release-version-mon-year’. For example, ‘release-0.1-Feb-2018’.

For the time being, releases don’t necessarily backwards compatibility. We will likely use semver-like semantics in the version of each release, though. For example, all release marked 1.x will probably comply with the 1.0 binary ABI.

Register and Bitfield Macros

There is a new way to define memory mapped I/O (MMIO) registers in Tock. It is intended as the full, unifying replacement for all of the other redundant register interfaces currently in use.

There are three types for working wit MMIO registers: ReadWrite, ReadOnly, and WriteOnly, providing read-write, read-only, and write-only functionality, respectively. Defining a set of registers is similar to the C-style approach, where each register is a field in a packed struct:

use common::regs::{ReadOnly, ReadWrite, WriteOnly};

#[repr(C, packed)]
struct Registers {
    cr: ReadWrite<u8, Control::Register>,
    s: ReadOnly<u8, InterruptFlags::Register>
    byte0: ReadWrite<u8>,
    byte1: ReadWrite<u8>,
    short: ReadWrite<u16>,
    word: ReadWrite<u32>
    // Etc.
}

The first parameter to each register type is its width (u8, u16, u32) and the second constrains the register to only use fields from a certain bitfield group.

Bitfields are defined through the register_bitfields! macro:

register_bitfields! [
    u8,

    Control [
        RANGE OFFSET(4) NUMBITS(3) [
            VeryHigh = 0,
            High = 1,
            Low = 2
        ],
        EN  OFFSET(3) NUMBITS(1) [],
        INT OFFSET(2) NUMBITS(1) []
    ],
    InterruptFlags [
        UNDES   10,
        TXEMPTY  9,
        NSSR     8,
        OVRES    3,
        MODF     2,
        TDRE     1,
        RDRF     0
    ]
]

The first parameter to register_bitfields! is the register width. Each subsequent parameter is register name and it’s associated bitfields, declared as name OFFSET(shift) NUMBITS(num) [ /* optional values */ ].

For example, the Control register, defined above, has a RANGE field at offset-bit 4 and size 3-bits which can take on values including VeryHigh, High and Low. The syntax is simpler for cases such as the InterruptFlags register defined above, where each field is a single bit and only the offset is necessary.

This interface helps the compiler catch some common types of bugs via type checking.

If you define the bitfields for eg a control register, you can give them a descriptive group name like Control. This group of bitfields will only work with a register of the type ReadWrite<_, Control> (or ReadOnly/WriteOnly, etc). For instance, if we have the bitfields and registers as defined above,

// This line compiles, because CR and regs.cr are both
// associated with the `Control` group of bitfields.
regs.cr.modify(Control::RANGE.val(1));

// This line will not compile, because CR is associated
// with the Control group, but regs.s is associated
// with the Status group.
regs.s.modify(Control::RANGE.val(1));

Pull Requests

Merged

Proposed