Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

arch/cortex-m: introduce CortexMVariant trait and avoid calls through extern "C" symbols #3080

Merged
merged 5 commits into from
Jul 22, 2022

Conversation

lschuermann
Copy link
Member

@lschuermann lschuermann commented Jul 11, 2022

Pull Request Overview

Given different sub-variants of the Cortex-M architecture family, this introduces a trait to encapsulate differences in between Cortex-M variants. Most prominently, this includes interrupt handlers which are now passed as associated constants of trait implementations versus exported symbols, as well as the switch_to_user function, which is now exposed as a regular function by trait implementors.

This has some significant benefits over passing functions via symbols:

  • By using a trait carrying proper first-level Rust functions, the type signatures of the trait and implementing functions are properly validated. Before these changes, some Cortex-M variants already used incorrect type signatures (e.g. *mut u8 instead of *const usize) for the user_stack argument. It also ensures that all functions are provided by a respective sub-architecture at compile time, instead of throwing linker errors.

  • Determining the respective functions at compile time, Rust might be able to perform more aggressive inlining, especially if more device-specific proper Rust functions (non hardware-exposed symbols, i.e. not fault or interrupt handlers) were to be added.

  • Most importantly, this avoid ambiguity with respect to a compiler fence being inserted by the compiler around calls to switch_to_user. The asm! macro in that function call will cause Rust to emit a compiler fence given the nomem option is not passed, but the opaque extern "C" function call obscured that code path. While this is probably fine and Rust is obliged to generate a compiler fence when switching to C code, having a traceable code path for Rust to the asm! macro will remove any remaining ambiguity and allow us to argue against requiring volatile accesses to userspace memory (during context switches). See document compiler fence before switching to userspace #2582 for further discussion of this issue.

Generally, this seems like the right way to go, which is further confirmed by our prior code using #[allow(improper_ctypes)] to annotate the extern "C" fn switch_to_user function reference.

The fact that some functions are proper trait functions, while others are exposed via associated constants is caused by the associated const functions being #[naked]. To wrap these functions in proper trait functions would require these trait functions to also be #[naked] to avoid generating a function prologue and epilogue, and we'd have to call the respective backing function from within an asm! block. The associated constants allow us to simply reference any function in scope and export it through the provided CortexMVariant trait infrastructure.

Testing Strategy

This pull request was tested by compiling. It still needs to be tested on a (few) ARM board(s). It has undergone basic tests on an nRF52DK (run an application, observe console output).

TODO or Help Wanted

N/A

Documentation Updated

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

Formatting

  • Ran make prepush.

@github-actions github-actions bot added nrf Change pertains to the nRF5x family of MCUs. sam4l Change pertains to the SAM4L MCU. stm32 Change pertains to the stm32 family of MCUSs labels Jul 11, 2022
@lschuermann lschuermann mentioned this pull request Jul 11, 2022
2 tasks
hudson-ayers
hudson-ayers previously approved these changes Jul 12, 2022
Copy link
Contributor

@hudson-ayers hudson-ayers left a comment

Choose a reason for hiding this comment

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

Some of the code movement in arch/cortex-m0/src/lib.rs made this a bit harder to review than it would have been, but overall I really like this. We had effectively been using raw function pointers to abstract over interfaces when traits are purpose built to do exactly that.

I have not tested this yet but I think it looks good.

@lschuermann
Copy link
Member Author

Some of the code movement in arch/cortex-m0/src/lib.rs made this a bit harder to review than it would have been

Right, sorry. I think that one part of the code had to be moved given the introduction of the traits, whereas other parts have been shuffled around to collect the naked fns in a single location, above the trait implementation. Assuming I didn't mess this up, this really should only change the order of functions defined there.

I agree that we should've been using traits before, but feel like the idea to use associated constants was a blocker to implementing this (at least took me some time to think of that).

Will test these changes first thing tomorrow when I have access to an ARM board again.

@lschuermann
Copy link
Member Author

Just tested this PR on my nRF52DK. I suppose just seeing the kernel greeting and interacting with the process console is sufficient to validate that this PR generally works, interrupt handlers are correctly referenced and called, so the associated constants idea seems to be the right way to go.

@lschuermann lschuermann force-pushed the dev/cortex-m-subarch-trait branch 2 times, most recently from b2e07f5 to 3a1fef6 Compare July 13, 2022 19:38
hudson-ayers
hudson-ayers previously approved these changes Jul 13, 2022
Copy link
Contributor

@bradjc bradjc left a comment

Choose a reason for hiding this comment

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

I didn't go through and double check every chip but based on the description this seems like a good change.

Given different sub-variants of the Cortex-M architecture family, this
introduces a trait to encapsulate differences in between Cortex-M
variants. Most prominently, this includes interrupt handlers which are
now passed as associated constants of trait implementations versus
exported symbols, as well as the `switch_to_user` function, which is
now exposed as a regular function by trait implementors.

This has some significant benefits over passing functions via symbols:

- By using a trait carrying proper first-level Rust functions, the
  type signatures of the trait and implementing functions are properly
  validated. Before these changes, some Cortex-M variants already used
  incorrect type signatures (e.g. `*mut u8` instead of `*const usize`)
  for the `user_stack` argument. It also ensures that all functions
  are provided by a respective sub-architecture at compile time,
  instead of throwing linker errors.

- Determining the respective functions at compile time, Rust might be
  able to perform more aggressive inlining, especially if more
  device-specific proper Rust functions (non hardware-exposed symbols,
  i.e. not fault or interrupt handlers) were to be added.

- Most importantly, this avoid ambiguity with respect to a compiler
  fence being inserted by the compiler around calls to
  `switch_to_user`. The `asm!` macro in that function call will cause
  Rust to emit a compiler fence given the `nomem` option is not
  passed, but the opaque `extern "C"` function call obscured that code
  path. While this is probably fine and Rust is obliged to generate a
  compiler fence when switching to C code, having a traceable code
  path for Rust to the `asm!` macro will remove any remaining
  ambiguity and allow us to argue against requiring volatile accesses
  to userspace memory (during context switches).

Generally, this seems like the right way to go, which is further
confirmed by our prior code using `#[allow(improper_ctypes)]` to
annotate the `extern "C" fn switch_to_user` function reference.

The fact that some functions are proper trait functions, while others
are exposed via associated constants is caused by the associated const
functions being `#[naked]`. To wrap these functions in proper trait
functions would require these trait functions to also be `#[naked]` to
avoid generating a function prologue and epilogue, and we'd have to
call the respective backing function from within an `asm!` block. The
associated constants allow us to simply reference any function in
scope and export it through the provided `CortexMSubarch` trait
infrastructure.

Signed-off-by: Leon Schuermann <leon@is.currently.online>
@lschuermann lschuermann force-pushed the dev/cortex-m-subarch-trait branch 2 times, most recently from e583052 to 91b2144 Compare July 21, 2022 13:50
@lschuermann lschuermann force-pushed the dev/cortex-m-subarch-trait branch from 91b2144 to c913626 Compare July 21, 2022 13:51
@lschuermann
Copy link
Member Author

Thanks for the feedback @hudson-ayers and @bradjc! Should be all addressed now.

@lschuermann lschuermann changed the title arch/cortex-m: introduce Subarch trait and avoid calls through extern "C" symbols arch/cortex-m: introduce CortexMVariant trait and avoid calls through extern "C" symbols Jul 21, 2022
Copy link
Contributor

@bradjc bradjc left a comment

Choose a reason for hiding this comment

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

Looks good

Copy link
Contributor

@hudson-ayers hudson-ayers left a comment

Choose a reason for hiding this comment

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

Thanks for documenting all of this so well

@bradjc bradjc added the last-call Final review period for a pull request. label Jul 22, 2022
@lschuermann
Copy link
Member Author

bors r+

@bors
Copy link
Contributor

bors bot commented Jul 22, 2022

@bors bors bot merged commit 224bad4 into tock:master Jul 22, 2022
bors bot added a commit that referenced this pull request Sep 7, 2022
3095: arm/cortex-m/syscall: replace volatile userspace RAM accesses with regular pointer reads/writes r=bradjc a=lschuermann

### Pull Request Overview

Given the changes introduced in the previous commits (introducing the CortexM subarch trait), which cause the Rust compiler to have direct visibility of the `#[naked]` functions and `asm!` macros used in handling interrupts and switching to userspace, it is obliged to insert a compiler fence before executing these blocks which potentially modify userspace memory.

Hence, volatile accesses to these memory regions are not actually needed, given that we only have a single thread of execution and retain total ordering of accesses in hardware.

This commit thus changes volatile memory accesses for regular accesses through pointers.

Signed-off-by: Leon Schuermann <leon@is.currently.online>

### Testing Strategy

To be tested! What's the best way to test this? Look at the LLVM-IR?


### TODO or Help Wanted

Blocked on #3080.


### Documentation Updated

- [x] ~Updated the relevant files in `/docs`,~ or no updates are required.

### Formatting

- [ ] Ran `make prepush`.


Co-authored-by: Leon Schuermann <leon@is.currently.online>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
last-call Final review period for a pull request. nrf Change pertains to the nRF5x family of MCUs. sam4l Change pertains to the SAM4L MCU. stm32 Change pertains to the stm32 family of MCUSs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants