Skip to content

Add only_once!() macro that panics if run a second time#4891

Closed
bradjc wants to merge 2 commits into
masterfrom
dev/only-once
Closed

Add only_once!() macro that panics if run a second time#4891
bradjc wants to merge 2 commits into
masterfrom
dev/only-once

Conversation

@bradjc

@bradjc bradjc commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Pull Request Overview

This allows us to do something like this:

struct MyDMAStructMustBeUnique {}

impl MyDMAStructMustBeUnique {
    pub fn new() -> Self {
        kernel::only_once!(MY_DMA_ONLY_ONCE);

        Self {}
    }
}

This is useful for DMA managers that must only be created once so that two different DMA managers can't conflict when trying to ensure memory is safely shared with DMA hardware.

This works much like static_init!() macro.

Testing Strategy

I tried this by putting this in a capsule constructor and creating a capsule twice in a board. This caused a panic.

TODO or Help Wanted

I just kinda made this up as a way to avoid needing to mark DMA manager constructors as unsafe. Is there a better way? Does this actually solve the safety issue?

Documentation Updated

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

Maybe we could document this somewhere, but I'm not sure where.

Formatting

  • Ran make prepush.

AI Use

  • The PR description details my use of AI in the production of the
    code in this PR, if any, and I have manually checked and
    personally certify the entire contents of this PR.

Just me.

bradjc added 2 commits June 19, 2026 18:13
This causes a panic if "called" twice. Can be used to ensure the same
object isn't constructed twice.
lschuermann

This comment was marked as duplicate.

@lschuermann lschuermann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See inline comment.

pub unsafe fn only_once_check_used(used: &mut bool) {
// Check if this bool has already been declared and initialized. If it
// has, then this is a call which is an error.
if *used {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This as written is unsound: it can enable unsynchronized (racing) read/writes to the static memory location. The update itself is also not atomic. As this is a static, there's no good way to make it single-threaded only. And even an AtomicBool won't work for any of our platforms that don't have CAS-like operations.

@lschuermann

Copy link
Copy Markdown
Member

To make sure this doesn't get lost, I want to point to the conversation on Matrix. I believe there is a platform-specific way to build "only-once" on platforms that don't have CAS-style atomics, something like this:

fn hart0_do_once(loc: *mut u8) {
  let mhartid = asm!(...);
  if mhartid != 0 { return false; }
  with_interrupts_disabled(|| {
    if *loc == 1 {
      false
    } else {
      *loc = 1;
      true
    }
  })
}

However, for various reasons, I think it's better and more truthful if we make the chain of driver construction unsafe, up to the main function.

@bradjc

bradjc commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

However, for various reasons, I think it's better and more truthful if we make the chain of driver construction unsafe, up to the main function.

My stance is the opposite: the benefits of having a chip crate with abstraction is to encapsulate the inherit unsafety in working with HW. Our job as OS designers is to figure out how to encapsulate the unsafe operations, and Tock kernel users shouldn't have to reason about low-level details of chip safety.

Additionally, the blunt tool that the unsafe keyword is in Rust makes it too easy to bundle complex restrictions that is difficult for developers to reason about. Once we have unsafe APIs, it is trivial to keep adding new unsafe code (and corresponding requirements), further complicating the responsibility for Tock users. And, as we've seen, once you allow unsafe, it grows, and is incredibly difficult to reverse.

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.

4 participants