rust-embedded / cortex-m Goto Github PK
View Code? Open in Web Editor NEWLow level access to Cortex-M processors
License: Apache License 2.0
Low level access to Cortex-M processors
License: Apache License 2.0
this will let us provide the cortex_m::asm
API on stable without depending on an external assembler.
TrustZone in Armv8-m processors like the Cortex-M23 and Cortex-M33 supports a second 'secure' copy of registers like SysTick and VTOR. It also supports a second interrupt vector table.
I'd like to add support, but it should be behind a feature flag as not all Cortex-M23/33 implementations support Secure Mode.
References:
I am using this crate indirectly from the stm32f1 crate when I try to document that library I'm getting an error with cortex-m.
cargo doc -p stm32f1 --open
Documenting cortex-m v0.5.2 error:
[::task::Poll]` cannot be resolved, ignoring it...
|
note: lint level defined here
--> /Users/billsb/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.5.2/src/lib.rs:42:9
|
42 | #![deny(warnings)]
| ^^^^^^^^
= note: #[deny(intra_doc_link_resolution_failure)] implied by #[deny(warnings)]
= note: the link appears in this line:
[`Poll::Pending`](::task::Poll). The future will *also* register the
^^^^^^^^^^^^
= help: to escape [
and ]
characters, just add '' before them like \[
or \]
error: [::task::Context::waker]
cannot be resolved, ignoring it...
|
= note: the link appears in this line:
[`cx.waker()`](::task::Context::waker)). Once a task has been woken up,
^^^^^^^^^^^^^^^^^^^^^^
= help: to escape [
and ]
characters, just add '' before them like \[
or \]
error: Could not document cortex-m
.`
Comparing something like:
Notably:
Send
is omitted from cortex_m::ITM
ptr() -> *mut RegisterBlock
and DerefMut
are removed from stm32f103xx::TIM6
I ran into this while trying to pass around ITM
in cortex_m_rtfm
, which wants Send
on resources
:
error[E0277]: the trait bound `*const (): core::marker::Send` is not satisfied in `cortex_m::peripheral::ITM`
--> src/main.rs:16:1
|
16 | / app! {
17 | | device: stm32f429,
18 | |
19 | | resources: {
... |
29 | | },
30 | | }
| |_^ `*const ()` cannot be sent between threads safely
|
= help: within `cortex_m::peripheral::ITM`, the trait `core::marker::Send` is not implemented for `*const ()`
= note: required because it appears within the type `core::marker::PhantomData<*const ()>`
= note: required because it appears within the type `cortex_m::peripheral::ITM`
I've used a quick wrapper to workaround:
...
use core::ops::{Deref,DerefMut};
pub struct ITM(cortex_m::peripheral::ITM);
impl Deref for ITM {
type Target = cortex_m::peripheral::ITM;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ITM {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
unsafe impl Send for ITM {}
...
app! {
...
resources: {
static R_ITM: ITM;
...
},
tasks: {
TIM7: {
path: periodic,
resources: [R_ITM, ...],
},
},
}
...
fn init(mut p: init::Peripherals) -> init::LateResources {
...
init::LateResources {
R_ITM: ITM(p.core.ITM),
R_TIM7: tim,
}
}
...
The SCB definition uses cfg(not(armv6m))
to choose whether or not to expose the VTOR
field on non-armv6m platforms. However, Cortex-M0+ has a VTOR
field: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0662b/CIHFDJCA.html
Since Cortex-M0 doesn't have a VTOR
field in its SCB
block I'm not sure what the correct solution here is. How do you feel about a feature that enables M0+ registers? It's not as elegant as architecture detection through the target.
#40 landed CMSIS-style DCache cleaning and invalidating by address and size, both usize
. This maps well to the actual hardware operations but is inconvenient to use.
We could add some convenience methods that operate on normal Rust objects:
clean_dcache_by_slice<T>(slice: &[T])
which passes slice.as_ptr() as usize
and core::mem::size_of<T>() * slice.len()
to the underlying methods,clean_dcache_by_ref<T>(obj: &T)
, which passes T as *const _ as usize
and core::mem::size_of<T>()
to the underlying methods.invalidate_dcache
and clean_invalidate_dcache
Big safety issue: all the cache methods operate on cache lines of 32 bytes. If you pass something whose size is not a multiple of 32, then some extra bytes after that object will be cleaned or invalidated. In general this is definitely unsafe. Perhaps these methods could panic on non-32-byte-multiple sizes?
I see the CPACR in scb
, but I do not see the Floating-point Status and Control Register (FPSCR), Floating-point Context Address Register (FPCAR), or Floating-point Context Control Register (FPCCR). AFAIK this is only an optional feature with Cortex-M4 and Cortex-M7 and should be included along with the CPACR behind the has_fpu
cfg, but I could be mistaken. See the User Guide for more info.
While attempting to create an example for rust-embedded/wg#259 to blink an LED using cortex_m::asm::delay
I'm greeted with this interesting error:
#![no_main]
#![no_std]
extern crate cortex_m;
extern crate cortex_m_rt;
extern crate panic_halt;
extern crate stm32f042_hal as hal;
use hal::prelude::*;
use hal::stm32;
use cortex_m::asm::delay;
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
let p = stm32::Peripherals::take().unwrap();
let gpioa = p.GPIOA.split();
let mut led = gpioa.pa1.into_push_pull_output();
loop {
led.set_high();
delay(8_000_000);
led.set_low();
delay(8_000_000);
}
}
error: linking with `rust-lld` failed: exit code: 1
|
= note: "rust-lld" "-flavor" "gnu" "-L" "/Users/egger/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/thumbv6m-none-eabi/lib" "/Users/egger/OSS/stm32f042-hal/target/thumbv6m-none-eabi/release/examples/blinky-b437c53f5b36f500.blinky.60cthedq-cgu.1.rcgu.o" "-o" "/Users/egger/OSS/stm32f042-hal/target/thumbv6m-none-eabi/release/examples/blinky-b437c53f5b36f500" "--gc-sections" "-L" "/Users/egger/OSS/stm32f042-hal/target/thumbv6m-none-eabi/release/deps" "-L" "/Users/egger/OSS/stm32f042-hal/target/release/deps" "-L" "/Users/egger/OSS/stm32f042-hal/target/thumbv6m-none-eabi/release/build/cortex-m-563ed60a5fde6333/out" "-L" "/Users/egger/OSS/stm32f042-hal/target/thumbv6m-none-eabi/release/build/cortex-m-rt-414ccd4438657f1e/out" "-L" "/Users/egger/OSS/stm32f042-hal/target/thumbv6m-none-eabi/release/build/stm32f0-f3baf49fdf34c49d/out" "-L" "/Users/egger/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/thumbv6m-none-eabi/lib" "--start-group" "-Bstatic" "/var/folders/8f/ch8vbtq93ldfc4vksdp5cw540000gn/T/rustcjqym5j/libcortex_m_rt-f3ac26fff634eea2.rlib" "/var/folders/8f/ch8vbtq93ldfc4vksdp5cw540000gn/T/rustcjqym5j/libcortex_m-945aa0e87bd515a7.rlib" "--end-group" "/Users/egger/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/thumbv6m-none-eabi/lib/libcompiler_builtins-962f36023deecec7.rlib" "-Tlink.x" "-Bdynamic"
= note: rust-lld: error: /var/folders/8f/ch8vbtq93ldfc4vksdp5cw540000gn/T/rustcjqym5j/libcortex_m-945aa0e87bd515a7.rlib(cortex-m.o):(.text.__delay+0x4): unrecognized reloc 103
right now you can write any u32
in the peripherals' registers. This is bad because you may write 1
s to reserved bits that are supposed to always be set to 0
.
svd2rust generates a "safer" API that prevents this kind of problem. But it generates the API from SVD files and I don't if there are SVD files for these core peripherals.
Should we manually implement an API like that? Or, perhaps, should we be more conservative and just provide bit masks (as const
s -- think const SCB_CPACR_CP10: u32 = 0b11 << 20
) to begin with? (the bit masks would save you the trouble of having to look into the documentation for the right bit offset of each bit field but they are not fool proof as you can still write to reserved bits)
cc @thejpster
The #[uninit]
attribute will place static [mut]
variables into an .uninit
section, located in RAM, that won't be initialized before main
.
Today, uninitialized (see MaybeUninit
) static
variables will be placed in
.bss
, which means that'll be zeroed before main
. This leads to unnecessary
work as leaving the memory uninitialized was the intended behavior.
The #[uninit]
attribute will have the following syntax.
#[uninit]
static mut FOO: u32 = {};
The initial value of uninit
variables must be the placeholder {}
. This
attribute will expand into:
#[link_section = ".uninit"]
static mut FOO: core::mem::MaybeUninit<u32> = core::mem::MaybeUninit::new();
#[uninit]
composes with #[entry]
and #[exception]
; it can be used on
safe local static mut
variables.
#[entry]
fn main() -> ! {
#[uninit]
static mut KEY: [u32; 8] = {};
// ..
// the name of this MaybeUninit method hasn't been decided but it writes
// a value into the MaybeUninit and then returns a reference to the written
// value
let key: &'static mut [u32; 8] = KEY.insert(/* runtime value */);
// ..
}
Before this can be implemented, MaybeUninit
must first land in the core
library. This feature will live behind a "nightly" (Cargo) feature until
MaybeUninit
and its const constructor are stabilized.
Option A
#[uninit]
static mut FOO: u32 = ();
Option B
#[uninit]
static mut FOO: u32 = ..;
We can't omit the RHS of static mut
because then the code won't parse and the
compiler will never reach the macro expansion phase.
This is not a perfect solution.
For example, a heapless
vector contains an uninitialized buffer when it's
constructed using the new
method but it also contains a length field that must
be initialized to zero. Applying #[uninit]
to such data structure means that
the vector will have to be initialized (assign 0 to its length field) at
runtime, which is not ergonomic. Not using #[uninit]
means that the vector
length and its buffer will be zeroed, which is wasteful.
It's not possible to have partial initialization of static
variables so this
is as good as it gets.
If you supply a target as a JSON file, the build.rs can't detect the target. This is because target
is a full path - in my case starts this starts C:\
, which != thumbv
.
I think the fix is to take the file_stem()
of the path and use that. This should work for both JSON targets and built-in targets (that don't contain .
). If the built-in target does use .
(e.g. thumbv8-m.base
) we'll need to be a bit cleverer.
This is the same as rust-embedded/cortex-m-rt#145.
See the mutex branch for an implementation of a mutex based on critical sections. And this PR for an example that uses that mutex to share state between the main thread and an interrupt.
The advantage of implementing them using critical sections is smaller code size, faster locking and that they don't require the LDREX or STREX instructions which are not available in the Cortex-M0(+).
If you have and can afford atomics then you should probably use the parking_lot crate.
cc @thejpster
via rust-embedded/svd2rust#117
Peripheral
, and maybe some other structs, aren't really Cortex-M specific. svd2rust currently relies on this crate just for that struct, but if it generates, say, some Rust code for a Cortex-A target instead, it still needs Peripheral
but nothing else.
First thought that comes to mind would be to move Peripheral
(and friends?) out into a separate crate, and have svd2rust and cortex-m rely on that generic crate instead. But, I'm sure there are many ways to do it.
Thoughts?
After cargo clean
everything worked again. Something somewhere is generating absolute paths, I guess. Maybe relative ones would be better.
I was looking into the default handler implementation and found some (seemingly) low-hanging fruit. I wanted to get your opinion before I made any changes though.
default_handler!
macro should include the trampoline and call the specified function with the exception frame as the argument. This would allow me to define my own handler without having to reinvent the trampoline, but it would break backward compatibility.PS C:\Users\david\Devel\cortex-m> cat .\build.ps1
$env:RUSTFLAGS="-Z linker-flavor=ld.lld -C linker=ld.lld.exe"
$env:CC="clang"
$env:AR="llvm-ar"
cargo build --target=thumbv6m-none-eabi --release
PS C:\Users\david\Devel\cortex-m> .\build.ps1
Compiling cortex-m v0.5.2 (file:///C:/Users/david/Devel/cortex-m)
error: failed to run custom build command for `cortex-m v0.5.2 (file:///C:/Users/david/Devel/cortex-m)`
process didn't exit successfully: `C:\Users\david\Devel\cortex-m\target\release\build\cortex-m-d6e8f84c8a15b15f\build-script-build` (exit code: 101)
--- stdout
TARGET = Some("thumbv6m-none-eabi")
OPT_LEVEL = Some("3")
TARGET = Some("thumbv6m-none-eabi")
HOST = Some("x86_64-pc-windows-msvc")
TARGET = Some("thumbv6m-none-eabi")
TARGET = Some("thumbv6m-none-eabi")
HOST = Some("x86_64-pc-windows-msvc")
CC_thumbv6m-none-eabi = None
CC_thumbv6m_none_eabi = None
TARGET_CC = None
CC = Some("clang")
TARGET = Some("thumbv6m-none-eabi")
HOST = Some("x86_64-pc-windows-msvc")
CFLAGS_thumbv6m-none-eabi = None
CFLAGS_thumbv6m_none_eabi = None
TARGET_CFLAGS = None
CFLAGS = None
DEBUG = Some("false")
running: "clang" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "--target=thumbv6m-none-eabi" "-Wall" "-Wextra" "-o" "C:\\Users\\david\\Devel\\cortex-m\\target\\thumbv6m-none-eabi\\release\\build\\cortex-m-9e47ce484a32dbc3\\out\\asm\\basepri_r.o" "-c" "asm/basepri_r.s"
cargo:warning=clang.exe: warning: argument unused during compilation: '-ffunction-sections' [-Wunused-command-line-argument]
cargo:warning=clang.exe: warning: argument unused during compilation: '-fdata-sections' [-Wunused-command-line-argument]
cargo:warning=asm/basepri_r.s:3:11: error: invalid operand for instruction
cargo:warning= mrs r0, BASEPRI
cargo:warning= ^
exit code: 1
--- stderr
thread 'main' panicked at '
Internal error occurred: Command "clang" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "--target=thumbv6m-none-eabi" "-Wall" "-Wextra" "-o" "C:\\Users\\david\\Devel\\cortex-m\\target\\thumbv6m-none-eabi\\release\\build\\cortex-m-9e47ce484a32dbc3\\out\\asm\\basepri_r.o" "-c" "asm/basepri_r.s" with args "clang" did not execute successfully (status code exit code: 1).
', C:\Users\david\.cargo\registry\src\github.com-1ecc6299db9ec823\cc-1.0.18\src\lib.rs:2181:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.
As far as I could tell the API does not currently allow setting the exception return value (EXC_RETURN
) in exception handlers, which would be useful for implementing context switching etc.
Could we add an extra case to the #[exception]
macro to also accept something like:
#[exception]
fn SysTick(exc: ExceptionReturn) -> ExceptionReturn
Where:
pub enum ExceptionReturn {
HandlerMsp,
HandlerFpuMsp,
ThreadMsp,
....
}
This enum can then be used under the hood to set the correct value for LR/PC upon exception return.
I'd be happy to further experiment with this idea and turn it into a pull request.
It's very likely that the thumb targets will soon switch to rust-lld as their default linker (see
rust-embedded/wg#160). If we want to achieve the goal of not depending on GCC, by default, to
build Cortex-M applications then we also have to make core crates like this one not depend on GCC by
default. This RFC is exactly about that.
I propose that in the next minor (breaking) release, v0.6.0, we tweak the meaning of the existing
"asm" Cargo feature and that we add a new Cargo feature named "inline-asm". The relationship between
these two features would be as follows:
If "asm" is disabled the cortex_m::asm
module will not be present. The crate will not depend
on GCC and will compile on stable / beta in this case. The state of the "inline-asm" feature will
be ignored in this scenario.
If "asm" is enabled and "inline-asm" is disabled then the cortex_m::asm
module will be
present and it will be implemented using external assembly files. The crate will depend on GCC
and will compile on stable / beta in this scenario.
If "asm" is enabled and "inline-asm" is also enabled then the cortex_m::asm
module will be
present and it will be implemented using inline assembly (i.e. asm!
). In this scenario, the
crate will depend on GCC and will require a nightly compiler to build.
When the Cortex-M API in core::arch::arm
becomes stabilized we'll expose a subset of the
cortex_m::asm
module (see #101) in the scenario where the "asm" feature is disabled. Remember
that when this feature is disabled the crate will not depend on GCC or on a nightly compiler.
compile_error!
) when both "asm" and "inline-asm" are enabled.cc @rust-embedded/cortex-m @hannobraun @crawford
On OSX:
rustc -V
rustc 1.27.0-nightly (ad610bed8 2018-04-11)
rustup target add thumbv7m-none-eabi
info: downloading component 'rust-std' for 'thumbv7m-none-eabi'
info: installing component 'rust-std' for 'thumbv7m-none-eabi'
cargo build --target thumbv7em-none-eabi
Compiling vcell v0.1.0
Compiling bare-metal v0.1.1
Compiling aligned v0.1.1
Compiling untagged-option v0.1.1
Compiling cortex-m v0.4.3 (file:///[snip]/cortex-m)
error[E0463]: can't find crate forcompiler_builtins
error[E0463]: can't find crate forcompiler_builtins
error[E0463]: can't find crate forcompiler_builtins
error[E0463]: can't find crate forcompiler_builtins
error: aborting due to previous error
For more information about this error, try
rustc --explain E0463
.
error: aborting due to previous error
error: aborting due to previous error
error: aborting due to previous error
For more information about this error, try
rustc --explain E0463
.
For more information about this error, tryrustc --explain E0463
.
For more information about this error, tryrustc --explain E0463
.
error: Could not compilealigned
.
warning: build failed, waiting for other jobs to finish...
error: Could not compileuntagged-option
.
warning: build failed, waiting for other jobs to finish...
error: Could not compilevcell
.
warning: build failed, waiting for other jobs to finish...
error: Could not compilebare-metal
.
What am doing wrong?
Currently, in the SCB peripheral, there is a field named shcrs
. However, the ARM manual refers to this register (the "System Handler Control and State Register") as SHCSR
.
I can open up a PR to change, however I wanted to check I am not missing something first :)
Hi, when running cargo doc
, the following errors are produced for this package:
Documenting cortex-m v0.5.2
error: `[::task::Poll]` cannot be resolved, ignoring it...
|
note: lint level defined here
--> /home/emifre/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.5.2/src/lib.rs:42:9
|
42 | #![deny(warnings)]
| ^^^^^^^^
= note: #[deny(intra_doc_link_resolution_failure)] implied by #[deny(warnings)]
= note: the link appears in this line:
[`Poll::Pending`](::task::Poll). The future will *also* register the
^^^^^^^^^^^^
= help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
error: `[::task::Context::waker]` cannot be resolved, ignoring it...
|
= note: the link appears in this line:
[`cx.waker()`](::task::Context::waker)). Once a task has been woken up,
^^^^^^^^^^^^^^^^^^^^^^
= help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
error: Could not document `cortex-m`.
I hope this helps to fix it :)
"Some nits about the SYST implementation: fn clear () does not make a whole lot of sense as clearing actually disables the SysTick timer because the exception is triggered when the count changes from 1 -> 0 which is also what causes the reload. Similarly the manuals suggest you should load the counter with the value 1 (for which there's currently no safe API) to ensure that the timer will not start with some offset in the first turn and also the correct value for the reload is actually - 1."
from rust-embedded/cortex-m-quickstart#15 (comment)
cc @whitequark
As discussed in rust-embedded/cortex-m-rt#44, cortex-m should provide a clean and safe interface for user configurable exceptions, for CM0 those are SVCall
, PendSV
and SysTick
; potentially there're others and/or more for higher processors.
#[late]
static are statics that are initialized at runtime, before the entry
point is called and after RAM initialization happens.
Consider the allocator example from the quickstart template:
#[global_allocator]
static ALLOCATOR: CortexMHeap = CortexMHeap::empty();
const HEAP_SIZE: usize = 1024; // in bytes
#[entry]
fn main() -> ! {
// Initialize the allocator BEFORE you use it
unsafe { ALLOCATOR.init(cortex_m_rt::heap_start() as usize, HEAP_SIZE) }
let xs = vec![0, 1, 2];
// ..
}
Quite a few things can go wrong there: you could forget to initialize the
allocator or you could initialize it too late. In either case you end up with
OOM (abort).
This example can be simplified using a #[late]
static:
#[late]
#[global_allocator]
static ALLOCATOR: SyncAllocator = unsafe {
static mut BYTES: [u8; 1024] = [0; 1024];
// you are familiar with this transformation
let bytes: &'static mut [u8] = BYTES;
// `SyncAllocator` is a newtype over `Mutex<SomeAllocator>`
SyncAllocator::new(bytes)
}
#[entry]
fn main() -> ! {
// Growable array allocated on the heap
let xs = vec![0, 1, 2];
// ..
}
#[late]
statics are not lazy statics. Lazy statics are initialized on
first use, they have runtime cost on each access and memory overhead. #[late]
statics are statics that are initialized before the #[entry]
function is
called, accessing them is zero cost and they have no memory overhead.
The above example expands to something like this:
// For details about the `.uninint` section see RFC rust-embedded/cortex-m#398
#[link_section = ".uninit.<random-hash>"]
#[global_allocator]
static ALLOCATOR: Late<SyncAllocator> = {
#[link_section = ".late.<random-hash>"] // new linker section
static INITIALIZER: unsafe fn() = initialize;
unsafe fn initialize() {
let val = {
// (proc-macro transformation)
let BYTES: &'static mut _ = {
static mut BYTES: [u8; 1024] = [0; 1024];
&mut BYTES
};
// (user code)
// you are familiar with this transformation
let bytes: &'static mut [u8] = BYTES;
// `SyncAllocator` is a newtype over `Mutex<Allocator>`
SyncAllocator::new(bytes)
};
ALLOCATOR.initialize(val);
}
Late::uninitialized()
};
The Late
newtype will be provided by this crate:
/// IMPLEMENTATION DETAIL. DO NOT USE
#[doc(hidden)]
pub struct Late<T> {
inner: UnsafeCell<MaybeUninit<T>>,
}
unsafe impl<T> Sync for Late<T> where T: Sync {}
impl<T> Late<T> {
/// IMPLEMENTATION DETAIL. DO NOT USE
#[doc(hidden)]
pub const fn uninitialized() -> Self {
Late {
inner: UnsafeCell::new(MaybeUninit::uninitialized()),
}
}
/// IMPLEMENTATION DETAIL. DO NOT USE
#[doc(hidden)]
pub unsafe fn initialize(&self, value: T) {
(*self.inner.get()).set(value)
}
}
impl<T> Deref for Late<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { (*self.inner.get()).get_ref() }
}
}
impl<T> DerefMut for Late<T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { (*self.inner.get()).get_mut() }
}
}
As you can see Deref
and DerefMut
perform no runtime checks. However,
dereferencing is only valid after Late
has been initialize
-d.
#[late]
statics are initialized before #[entry]
/ main
and after RAM
initialization using r0::run_init_array
or its equivalent.
fn Reset() -> ! {
__pre_init();
r0::zero_bss(&mut __sbss, &mut __ebss);
r0::init_data(&mut __sdata, &mut __edata, & __sidata);
r0::run_init_array(&__slate, &__elate);
main()
}
#[global_allocator]
To compose with the built-in global_allocator
attribute we need to provide
the implementation below in this crate.
unsafe impl<T> GlobalAlloc for Late<T>
where
T: GlobalAlloc,
{
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
T::alloc(self.deref(), layout)
}
unsafe fn dealloc(&self, p: *mut u8, layout: Layout) {
T::dealloc(self.deref(), p, layout)
}
}
unsafe
?Using normal static
s within a #[late]
static initializer is sound, but using
any #[late]
static within the initializer is not because there are no
guarantees about the execution order of #[late]
initializers.
Furthermore for global resources like global_allocator
trying to use the
resource before it's initialized is also undefined behavior.
#[late]
#[global_allocator]
static ALLOCATOR: SyncAllocator = unsafe {
static mut BYTES: [u8; 1024] = [0; 1024];
let xs = vec![0, 1, 2]; // undefined behavior
ALLOCATOR.alloc(..); // also undefined behavior
SyncAllocator::new(BYTES)
}
It's not possible to prevent all these issues at compile time so unsafe
must
be used. Arguably, we could prevent the first issue by making the static
unnameable (by assigning it a randomly generated name) but this would greatly
reduce the flexibility of #[late]
statics.
Thoughts? Are there use cases other than global allocators?
This compiles:
fn main() {
cortex_m::interrupt::free(|cs| {
cortex_m::interrupt::enable();
});
}
I suppose it's obvious why it's problematic...
In 2018
edition instead of extern crate cortex_m_rt as rt
one would use Cargo rename-dependency
:
[dependencies]
rt = {version = "0.6.2", package='cortex-m-rt'}
cortex_m_rt_macros, however, doesn't respect this and still generates code with extern crate cortex_m_rt
.
Currently if and exception or a non-implemented interrupt triggered the default_handler function called. In case if no debugger connected it appears as freeze. This can be way more friendly by adding output to std-out as the configurable feature:
extern "C" fn handler(_sr: &StackedRegisters) -> ! {
#[cfg(exception_print = "stdout")]
{
println!("EXCEPTION {:?} @ PC=0x{:08x} LR=0x{:08x} XPSR=0x{:08x}", Exception::current(), _sr.pc, _sr.lr, _sr.xpsr);
println!("R0=0x{:08x} R1=0x{:08x} R2=0x{:08x} R3=0x{:08x} R12=0x{:08x}", _sr.r0, _sr.r1, _sr.r2, _sr.r3, _sr.r12);
}
or similar.
I've tripped over cortex_m::register::primask::Primask not implementing Debug but there might be others.
It took me a long while to debug why linker script behaves erratically and sometimes compiles right, but other times complaints either about the lack of exception handlers or too many of them if I attempt to add them. Turns out, all it took is to disable CARGO_INCREMENTAL
environment variable.
The IPR registers are modelled as an array of byte-sized registers. I believe this is fine on ARMv7-M, as the ARMv7-M Architecture Reference Manual (link requires registration), section B.3.4.9 states:
The registers are byte, aligned halfword, and word accessible.
However, the situation is different on ARMv6-M. The ARMv6-M Architecture Reference Manual (link requires registration), section B3.4.7 states:
Subject to standard PPB usage constraints, see General rules for PPB register accesses on page B3-260.
Which states (section B3.1.1):
The PPB address space only supports aligned word accesses. Byte and halfword access is UNPREDICTABLE.
This aligns with my own observation, that setting interrupt priority on an ARM Cortex-M0+ via NVIC::set_priority
doesn't seem to have any effect.
I think the right way to fix this is to model the ipr
field as an array of 32-bit registers, which should be compatible with both architectures. I should be able to put together a pull request by tomorrow, or next week at the latest.
This may be getting a bit too complex to call "minimal", but to support applications that require some permanent storage there needs to be a section defined for that. This probably requires adding the flash page size into memory.x
, then having a section something like
.storage BLOCK(_page_size) (NOLOAD) :
{
FILL(0xFF)
*(.storage .storage.*)
. = ALIGN(_page_size);
} > FLASH
If possible, aligning in between each .storage.*
section would be wonderful to allow having separately erasable sections (although, I don't know any way to do so).
(Note, I have only used flash storage on the nrf51, I assume other cortex-m devices behave similarly, but this may not be general enough).
It's something of a footgun: since the body of main must be protected by cortex_m::interrupt::free
whether I want it or not, it's easy to assume that interrupts are enabled outside of that critical section, and forget to add an explicit cortex_m::interrupt::enable();
after it.
It does seem odd when coming from C, but with interrupt safety by default, I can't think of any real downsides...
src/peripherals
is pretty much undocumented in this regard.
I am currently unable to run my application standalone when a debugger is not present. I am suspecting that it is because the TPIU and the ITM port are not enabled by my code. Removing traces fixes my issue.
Would it be a good idea to check if tracing is enabled like ITM_SendChar ?
If so, I would like to submit a pull request to short circuit write_all if tracing is not enabled.
There's no API for controlling the SLEEPDEEP bit in the SCB's SCR register. It would be great to have a nice API for doing that.
I plan to take a look at this, but I won't be able to any time soon. If anyone wants to pick this up, please go ahead!
Now that scoped singletons are a thing, it makes sense to change some method signatures from taking &self
to taking &mut self
. An example of this is the NVIC API. See comments by @japaric in #62.
I plan to take a look at this, but I won't be able to any time soon. If anyone wants to pick this up, go ahead!
There is currently no support for working in privileged mode.
There should at least be
cortex_m::asm::svc(num: u32)
cortex_m::register::control::write(Control)
and perhaps also something in the lines of cortex_m::register::control::modify(f: F) where f: FnOnce(Control) -> Control
The cortex_m_rt crate seem to already implement the possibility for overriding the SVC handler. So with these changed it should be possible to:
See #40. The CLIDR
, CTR
, CCSIDR
, and CSSELR
registers are only implemented on ARMv7-M and shouldn't appear for ARMv6-M. As this is a breaking change, wait until the next breaking release.
to match the changes in svd2rust.
Hi,
So I am very impressed by all these arm crates, and after reading the blog entries wanted to try it out, and see if I can get something blinking on my Teensy Board.
I am wondering about the workflow here. In the Rust your ARM microcontroller! blogpost manual SVD -> Rust crate conversion is described. I was wondering if this could be done automatically. Eg. in build.rs
one could call some function to generate the necessary files during the build itself. Does such approach make sense?
BTW. Is there a place like IRC/gitter to hang around to lurk at the discussion around this crate?
What do you think about having a struct like zinc's NoInterrupts struct and passing it into the closure for the interrupt::free
function? The idea is to make a zero-sized struct that can't be created by users and is only ever created in a critical section. Then, if a function should only be called in a critical section it can take a reference to that struct which will guarantee that it will only be called in a critical section.
It would look something like this
mod interrupts {
pub fn disable() {}
pub fn enable() {}
pub struct CritToken {
_private: ()
}
pub fn free<F>(f: F)
where F: FnOnce(&CritToken)
{
disable();
f(&CritToken{ _private: () });
enable();
}
}
fn dangerous_func(tok: &interrupts::CritToken) {}
fn calling_dangerous_func() {
interrupts::free(|tok| {
dangerous_func(tok);
});
}
Hi all,
IMO the singleton macro is nothing cortex-m
specific, it would also apply to other architectures and CPUs. It would make sense moving it to https://github.com/japaric/bare-metal. We have done something like that in the past already I see (#50).
Thoughts?
BR,
Andre
The ITM can easily be enabled using OpenOCD but that requires having a debugger connected to the MCU. ITM can be used for logging or as a communication channel in a production application; in that case the MCU must take care of initializing the ITM. Right now enabling the ITM requires unsafe code; this is what it looks like:
unsafe {
// enable TPIU and ITM
cp.DCB.demcr.modify(|r| r | (1 << 24));
// prescaler
let swo_freq = 2_000_000;
cp.TPIU.acpr.write((clocks.sysclk().0 / swo_freq) - 1);
// SWO NRZ
cp.TPIU.sppr.write(2);
cp.TPIU.ffcr.modify(|r| r & !(1 << 1));
// STM32 specific: enable tracing in the DBGMCU_CR register
const DBGMCU_CR: *mut u32 = 0xe0042004 as *mut u32;
let r = ptr::read_volatile(DBGMCU_CR);
ptr::write_volatile(DBGMCU_CR, r | (1 << 5));
// unlock the ITM
cp.ITM.lar.write(0xC5ACCE55);
cp.ITM.tcr.write(
(0b000001 << 16) | // TraceBusID
(1 << 3) | // enable SWO output
(1 << 0), // enable the ITM
);
// enable stimulus port 0
cp.ITM.ter[0].write(1);
}
We should have a safe API to enable the ITM. The API could be an ITM::enable(&mut self, prescaler: u32)
method to globally enable the ITM and an ITM::{enable,disable}_port(&mut self, i: u8)
to enable / disable an individual port.
I was just looking at ways to cheaply and clippy happy "terminate" an #[entry]
function and was looking at the cortex_m::asm:bkpt()
function noticing that it has a signature of pub fn bkpt()
. My expectation would have been that this function has a signature of pub fn bkpt() -> !
instead which would qualify it as a terminating statement in #[entry]
.
Is there a reason for bkpt()
not returning never
?
Let's say FPU is disabled at entry in main()
as it usually is, and you want to do some FP operations. Presto!
fn main() {
cortex_m::interrupt::free(|cs| {
let scb = tm4c129x::SCB.borrow(cs);
scb.enable_fpu();
// some FP-using code
});
}
... or something like that. Well, not so easy. Since the code below enable_fpu
changes callee-save FP registers, LLVM will try to stack those at the top of main... using the vpush
instruction. Which is not available until FPU is enabled. Oops.
I don't know how to fix this.
Currently it's not possible to run code that contains semihosting calls without having a debugger attached, since semihosting relies on the debugger catching bkpt 0xab
and without a debugger, this will trigger an exception and halt the mcu in most cases. I read that some semihosting C runtime provides an exception handler that is able to ignore this exception so that the mcu keeps running even without a debugger attached.
Even though semihosting is not meant to be used without a debugger, it might be useful to be able to run without one. It should at least be mentioned in the docs to make developers aware of it. Otherwise they might wonder why their code stops working when they plug out the debugger.
Unfortunately, this crate can't patch the vector table to catch and ignore the exception automatically, but we could try to find a way to make it trivial for the user to install such a handler (like providing the handler and the user just has to add it to the vector table)
I have an interrupt handler which is called very frequently and due to the CPU running at high speed I have to use wait states which seems to slow down processing quite a bit. Is there any simple way to declare that the IRQ handler should be copied to RAM during initialisation instead of being run from flash?
I've been working on making an embedded-hal implementation for the stm32f469 and building up to a board specific crate for the stm32f469i discovery board and have got to the point where I've started trying to flash some LEDs but I encountered a hard fault when executing Primask::read
Here is the project I'm programming onto the board link and below is some output from gdb from when I was stepping through. The fault's probably my own so any help would be appreciated!
Breakpoint 1, leds_on::main::hfa770daafd2bee96 () at examples/leds_on.rs:30
30 let p = stm32f469xx::Peripherals::take().unwrap();
>>> step
stm32f469xx::Peripherals::take::h6b0a9d4241464a77 ()
at /home/xd009642/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32f469xx-0.2.1/src/lib.rs:473549
473549 cortex_m::interrupt::free(|_| {
>>> step
cortex_m::interrupt::free::he28aea6b14b68ad8 (f=...)
at /home/xd009642/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.5.2/src/interrupt.rs:64
64 let primask = ::register::primask::read();
>>> step
cortex_m::register::primask::read::h1846bf7d2763250f ()
at /home/xd009642/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.5.2/src/register/primask.rs:44
44 unsafe { __primask() }
>>> step
HardFault () at asm.s:4
4 mrs r0, MSP
>>> step
5 bl UserHardFault
>>> step
UserHardFault (ef=0x2004fef0) at <exception macros>:10
10 <exception macros>: No such file or directory.
>>> step
leds_on::safe_state::hd0df1968d7543a7e (_ef=0x2004fef0) at examples/leds_on.rs:26
26 loop {}
>>> step
EDIT: Done some peeking and poking of the ARM fault status registers and this hard fault is down to an undefined instruction. These appear to be at the end of the panics but it doesn't appear to hit a panic.
Since #17, identical copies of exception::default_handler
get included in the binary, thanks to the token generic parameter. It's not a lot of code, but it seems a bit unfortunate. Is there any way around this?
Thanks for all the work you do for embedded Rust, btw! <3
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.