Skip to content

Conversation

@bradjc
Copy link
Contributor

@bradjc bradjc commented Dec 5, 2025

Pull Request Overview

For a long time, Tock's kernel-provided panic utilities required some type that implements IoWrite to display panic messages. How a board created such a type was up to each board, and the io.rs files we have in boards have used a variety of methods to do this. This has had the effect of creating three issues:

  1. These types (that implement IoWrite) are often instantiated as static muts, which we are trying to get rid of.
  2. These types often make assumptions about the state of the underlying hardware when the panic happens, and assume the kernel initialized the hardware correctly and that the object used by the normal kernel operation can still be used during a panic.
  3. Implementations of the types are duplicated across many boards, sometimes identically and sometimes with minor changes.

To try to fix these issues, this PR (from the static mut task force) proposes a new trait PanicWriter that chips would implement to provide a synchronous writer suitable for panic messages. Boards then only need to select the implementation of PanicWriter they want to use for panics, and the chip-provided implementation is used by debug.rs to correctly display the panic message.

The trait looks like this:

pub trait PanicWriter {
    /// The configuration data the mechanism needs to configure the writer for panic output.
    type Config;

    /// Create a new synchronous writer capable of sending panic messages.
    unsafe fn create_panic_writer(config: Self::Config) -> impl IoWrite;
}

The config type permits the board to provide whatever settings are necessary for the implementation to correctly configure the hardware for panic output.

This addresses the three concerns by:

  1. The create_panic_writer() function must instantiate the object on the stack, so it is not declared as a static mut.
  2. The chip-provided implementations must create a new hardware object instantiation, eliminating sharing between the main kernel and the panic handler. Also, the config provides a method for communicating exactly the settings needed by the writer.
  3. The implementations are provided by chips and not each board, eliminating redundancy.

Testing Strategy

todo

TODO or Help Wanted

Thoughts?

Need to port every chip.

Documentation Updated

  • Updated the relevant files in /docs, or no updates are required.

Formatting

  • Ran make prepush.

Copy link
Member

@lschuermann lschuermann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I agree with this approach. Given the implementation of PanicWriter will be chip-specific, and should carefully reason about whether an implementation of PanicWriter is actually safe for each UART driver, I think it might make sense to split these up over multiple PRs.

Comment on lines +192 to +198
/// Create a new synchronous writer capable of sending panic messages.
///
/// The constructed writer must be created on the stack. Because panic
/// will never return this is effectively a static allocation.
///
/// The writer must implement [`IoWrite`].
unsafe fn create_panic_writer(config: Self::Config) -> impl IoWrite;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't really enforce that the return value will be stored on the stack, and I don't think it actually matters.

What does matter are the other guarantees we ought to require callers of this unsafe function to uphold:

  • the function must only be called once: that ensures that we will never have two instances of the resulting impl IoWrite around that may be trampling over each other, and
  • this function must only be called in the panic handler. In particular, after this function is called, no other code will access the hardware backing this writer. In a sense, this function will forcefully "take over" the underlying hardware, whichever state it will be in. A full system reset is required before any other code is allowed to access the underlying hardware again.

These promises by the caller are, I think, required to build reliable and sound panic writer implementations.

Comment on lines +615 to +627
unsafe fn create_panic_writer(config: Self::Config) -> impl IoWrite {
use uart::Configure as _;

let inner = Uarte::new(UARTE0_BASE);
inner.initialize(
pinmux::Pinmux::from_pin(config.txd),
pinmux::Pinmux::from_pin(config.rxd),
config.cts.map(|c| unsafe { pinmux::Pinmux::from_pin(c) }),
config.rts.map(|r| unsafe { pinmux::Pinmux::from_pin(r) }),
);
let _ = inner.configure(config.params);
UartPanicWriter { inner }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this safe regardless of which state the UART is in? Do we need to cancel any (potentially) in-progress DMA operations?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants