diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..518037df --- /dev/null +++ b/.travis.yml @@ -0,0 +1,85 @@ +language: rust + +matrix: + include: + - env: TARGET=x86_64-unknown-linux-gnu + + - env: TARGET=thumbv6m-none-eabi + rust: beta + addons: + apt: + packages: + - gcc-arm-none-eabi + + - env: TARGET=thumbv7m-none-eabi + rust: beta + addons: + apt: + packages: + - gcc-arm-none-eabi + + - env: TARGET=thumbv7em-none-eabi + rust: beta + addons: + apt: + packages: + - gcc-arm-none-eabi + + - env: TARGET=thumbv7em-none-eabihf + rust: beta + addons: + apt: + packages: + - gcc-arm-none-eabi + + - env: TARGET=thumbv6m-none-eabi + rust: nightly + addons: + apt: + packages: + - gcc-arm-none-eabi + + - env: TARGET=thumbv7m-none-eabi + rust: nightly + addons: + apt: + packages: + - gcc-arm-none-eabi + + - env: TARGET=thumbv7em-none-eabi + rust: nightly + addons: + apt: + packages: + - gcc-arm-none-eabi + + - env: TARGET=thumbv7em-none-eabihf + rust: nightly + addons: + apt: + packages: + - gcc-arm-none-eabi + +before_install: set -e + +install: + - bash ci/install.sh + +script: + - bash ci/script.sh + +after_script: set +e + +cache: cache + +before_cache: + - chmod -R a+r $HOME/.cargo; + +branches: + only: + - staging + - trying + +notifications: + email: + on_success: never diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dbed873..dc074084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [v0.5.0] - 2018-05-12 + +### Added + +- An `entry!` macro to set the entry point of the program. + +- A `heap_start` function that returns a pointer into the start of the heap region. + +- A `device` feature. When disabled this crate provides the interrupt vectors; when enabled the + interrupt vectors are expected to be provided by another crate. Read the documentation for + details. + +### Changed + +- This crate now compiles on the beta and stable channels. + +- [breaking-change] this crate now requires `arm-none-eabi-gcc` to be installed and available in + `$PATH` to compile. + +- [breaking-change] the `start` lang item has been removed. The standard `main` interface won't + work. Instead use `#![no_main]` and the `entry!` macro. See documentation for details. + +- [breaking-change] the `default_handler!` macro has been merged into the `exception!` macro. Use + `exception!(*, ..)` to set the default exception handler. + +- [breaking-change] there's no weak default handler so a default handler must be defined by the + application, or one of its dependencies. + +- [breaking-change] the syntax of the third argument of the `exception!` handler has changed. See + the documentation of the macro for details. + +- [breaking-change] the exception names that the `exception!` macro accepts has changed to match the + CMSIS specification. See the documentation of the macro for the list of names it accepts. + +- [breaking-change] The number of symbol interfaces has been reduced. Check the advanced section of + the documentation for details. + ## [v0.4.0] - 2018-04-09 ### Added diff --git a/Cargo.toml b/Cargo.toml index cd31b2db..460917b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,16 @@ keywords = ["arm", "cortex-m", "runtime", "startup"] license = "MIT OR Apache-2.0" name = "cortex-m-rt" repository = "https://github.com/japaric/cortex-m-rt" -version = "0.4.0" +version = "0.5.0" + +[build-dependencies] +cc = "1.0.10" [dependencies] -cortex-m = "0.3.0" -r0 = "0.2.1" \ No newline at end of file +r0 = "0.2.1" + +[dev-dependencies] +panic-semihosting = "0.2.0" + +[features] +device = [] diff --git a/asm.s b/asm.s new file mode 100644 index 00000000..2937be84 --- /dev/null +++ b/asm.s @@ -0,0 +1,5 @@ + .global HardFault + .thumb_func +HardFault: + mrs r0, MSP + bl UserHardFault diff --git a/bors.toml b/bors.toml new file mode 100644 index 00000000..5ccee21e --- /dev/null +++ b/bors.toml @@ -0,0 +1,3 @@ +status = [ + "continuous-integration/travis-ci/push", +] \ No newline at end of file diff --git a/build.rs b/build.rs index 1d0160c0..1b5c3d1e 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,5 @@ +extern crate cc; + use std::env; use std::fs::File; use std::io::Write; @@ -7,14 +9,48 @@ fn main() { let target = env::var("TARGET").unwrap(); has_fpu(&target); - is_armv6m(&target); + let is_armv6m = is_armv6m(&target); + + if target.starts_with("thumbv") { + cc::Build::new().file("asm.s").compile("asm"); + } // Put the linker script somewhere the linker can find it let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("link.x")) - .unwrap() - .write_all(include_bytes!("link.x")) - .unwrap(); + let link_x = include_bytes!("link.x.in"); + let mut f = if env::var_os("CARGO_FEATURE_DEVICE").is_some() { + let mut f = File::create(out.join("link.x")).unwrap(); + + writeln!( + f, + r#" +/* Provides weak aliases (cf. PROVIDED) for device specific interrupt handlers */ +/* This will usually be provided by a device crate generated using svd2rust (see `device.x`) */ +INCLUDE device.x"# + ).unwrap(); + f.write_all(link_x).unwrap(); + f + } else { + let mut f = File::create(out.join("link.x")).unwrap(); + f.write_all(link_x).unwrap(); + f + }; + + let max_int_handlers = if is_armv6m { 32 } else { 240 }; + + // checking the size of the interrupts portion of the vector table is sub-architecture dependent + writeln!( + f, + r#" +ASSERT(__einterrupts - __eexceptions <= 0x{:x}, " +There can't be more than {} interrupt handlers. This may be a bug in +your device crate, or you may have registered more than 240 interrupt +handlers."); +"#, + max_int_handlers * 4, + max_int_handlers + ).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); println!("cargo:rerun-if-changed=build.rs"); @@ -27,8 +63,11 @@ fn has_fpu(target: &str) { } } -fn is_armv6m(target: &str) { +fn is_armv6m(target: &str) -> bool { if target.starts_with("thumbv6m-") { println!("cargo:rustc-cfg=armv6m"); + true + } else { + false } } diff --git a/ci/install.sh b/ci/install.sh new file mode 100644 index 00000000..3c419211 --- /dev/null +++ b/ci/install.sh @@ -0,0 +1,9 @@ +set -euxo pipefail + +main() { + if [ $TARGET != x86_64-unknown-linux-gnu ]; then + rustup target add $TARGET + fi +} + +main diff --git a/ci/script.sh b/ci/script.sh new file mode 100644 index 00000000..221386df --- /dev/null +++ b/ci/script.sh @@ -0,0 +1,34 @@ +set -euxo pipefail + +main() { + cargo check --target $TARGET + + cargo check --target $TARGET --features device + + local examples=( + minimal + main + state + ) + if [ $TRAVIS_RUST_VERSION = nightly ]; then + for ex in "${examples[@]}"; do + cargo rustc --target $TARGET --example $ex -- \ + -C link-arg=-nostartfiles \ + -C link-arg=-Wl,-Tlink.x + + cargo rustc --target $TARGET --example $ex --release -- \ + -C link-arg=-nostartfiles \ + -C link-arg=-Wl,-Tlink.x + done + + cargo rustc --target $TARGET --example device --features device -- \ + -C link-arg=-nostartfiles \ + -C link-arg=-Wl,-Tlink.x + + cargo rustc --target $TARGET --example device --features device --release -- \ + -C link-arg=-nostartfiles \ + -C link-arg=-Wl,-Tlink.x + fi +} + +main diff --git a/device.x b/device.x new file mode 100644 index 00000000..28f975e0 --- /dev/null +++ b/device.x @@ -0,0 +1,3 @@ +/* Sample device.x file */ +PROVIDE(WWDG = DefaultHandler); +PROVIDE(PVD = DefaultHandler); diff --git a/examples/device.rs b/examples/device.rs new file mode 100644 index 00000000..cf91f211 --- /dev/null +++ b/examples/device.rs @@ -0,0 +1,50 @@ +//! Manually create the interrupts portion of the vector table + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +extern crate panic_semihosting; + +use rt::ExceptionFrame; + +// the program entry point +entry!(main); + +fn main() -> ! { + loop {} +} + +// the hard fault handler +exception!(HardFault, hard_fault); + +fn hard_fault(_ef: &ExceptionFrame) -> ! { + loop {} +} + +// the default exception handler +exception!(*, default_handler); + +fn default_handler(_irqn: i16) {} + +// interrupts portion of the vector table +pub union Vector { + handler: unsafe extern "C" fn(), + reserved: usize, +} + +extern "C" { + fn WWDG(); + fn PVD(); +} + +#[link_section = ".vector_table.interrupts"] +#[no_mangle] +pub static __INTERRUPTS: [Vector; 3] = [ + Vector { handler: WWDG }, + Vector { reserved: 0 }, + Vector { handler: PVD }, +]; diff --git a/examples/main.rs b/examples/main.rs new file mode 100644 index 00000000..d3192494 --- /dev/null +++ b/examples/main.rs @@ -0,0 +1,28 @@ +//! Directly plug a `main` symbol instead of using `entry!` + +#![deny(warnings)] +#![no_main] +#![no_std] + +#[macro_use(exception)] +extern crate cortex_m_rt as rt; +extern crate panic_semihosting; + +use rt::ExceptionFrame; + +#[no_mangle] +pub unsafe extern "C" fn main() -> ! { + loop {} +} + +// the hard fault handler +exception!(HardFault, hard_fault); + +fn hard_fault(_ef: &ExceptionFrame) -> ! { + loop {} +} + +// the default exception handler +exception!(*, default_handler); + +fn default_handler(_irqn: i16) {} diff --git a/examples/minimal.rs b/examples/minimal.rs new file mode 100644 index 00000000..c12d12dd --- /dev/null +++ b/examples/minimal.rs @@ -0,0 +1,31 @@ +//! Minimal `cortex-m-rt` based program + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +extern crate panic_semihosting; + +use rt::ExceptionFrame; + +// the program entry point +entry!(main); + +fn main() -> ! { + loop {} +} + +// the hard fault handler +exception!(HardFault, hard_fault); + +fn hard_fault(_ef: &ExceptionFrame) -> ! { + loop {} +} + +// the default exception handler +exception!(*, default_handler); + +fn default_handler(_irqn: i16) {} diff --git a/examples/state.rs b/examples/state.rs new file mode 100644 index 00000000..0b5eeebc --- /dev/null +++ b/examples/state.rs @@ -0,0 +1,38 @@ +//! Preserving state across executions of an exception handler + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +#[macro_use(entry, exception)] +extern crate cortex_m_rt as rt; +extern crate panic_semihosting; + +use rt::ExceptionFrame; + +// the program entry point +entry!(main); + +fn main() -> ! { + loop {} +} + +// exception handler with state +exception!(SysTick, sys_tick, state: u32 = 0); + +fn sys_tick(state: &mut u32) { + *state += 1; +} + +// the hard fault handler +exception!(HardFault, hard_fault); + +fn hard_fault(_ef: &ExceptionFrame) -> ! { + loop {} +} + +// the default exception handler +exception!(*, default_handler); + +fn default_handler(_irqn: i16) {} diff --git a/link.x b/link.x deleted file mode 100644 index 7e398dee..00000000 --- a/link.x +++ /dev/null @@ -1,125 +0,0 @@ -INCLUDE memory.x - -/* With multiple codegen units the rlib produced for this crate has several object files in it. */ -/* Because the linker is Smart it may not look into all the object files and not pick up the */ -/* .vector_table.exceptions section. But we want it to! To workaround the problem we create an */ -/* undefined reference to the EXCEPTIONS symbol (located in .vector_table.exceptions); this way the */ -/* linker will look at all the object of the rlib and pick up our EXCEPTIONS symbol */ -EXTERN(EXCEPTIONS); - -/* Create an undefined reference to the INTERRUPTS symbol. This is required to - force the linker to *not* drop the INTERRUPTS symbol if it comes from an - object file that's passed to the linker *before* this crate */ -EXTERN(INTERRUPTS); - -PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); - -SECTIONS -{ - .vector_table ORIGIN(FLASH) : ALIGN(4) - { - /* Vector table */ - _svector_table = .; - LONG(_stack_start); - - KEEP(*(.vector_table.reset_vector)); - - KEEP(*(.vector_table.exceptions)); - _eexceptions = .; - - KEEP(*(.vector_table.interrupts)); - _einterrupts = .; - } > FLASH - - PROVIDE(_stext = _einterrupts); - - .text _stext : ALIGN(4) - { - /* Put reset handler first in .text section so it ends up as the entry */ - /* point of the program. */ - KEEP(*(.reset_handler)); - - *(.text .text.*); - } > FLASH - - .rodata : ALIGN(4) - { - *(.rodata .rodata.*); - . = ALIGN(4); - } > FLASH - - PROVIDE(_sbss = ORIGIN(RAM)); - .bss _sbss : ALIGN(4) - { - *(.bss .bss.*); - . = ALIGN(4); - _ebss = .; - } > RAM AT > FLASH - /* NOTE(AT > FLASH) without this LLD v6 produces a binary that crashes OpenOCD whereas LLD v7 - emits a ".rodata and .bss sections overlap" error ... This hacky workaround doesn't increase - the binary size AFAICT */ - - .data : ALIGN(4) - { - _sidata = LOADADDR(.data); - _sdata = .; - *(.data .data.*); - . = ALIGN(4); - _edata = .; - } > RAM AT > FLASH - - /* The heap starts right after the .bss + .data section ends */ - _sheap = _edata; - - /* fake output .got section */ - /* Dynamic relocations are unsupported. This section is only used to detect - relocatable code in the input files and raise an error if relocatable code - is found */ - .got : - { - _sgot = .; - KEEP(*(.got .got.*)); - _egot = .; - } > RAM AT > FLASH - - /DISCARD/ : - { - *(.ARM.exidx.*); - } -} - -/* Do not exceed this mark in the error messages below | */ -ASSERT(_eexceptions - ORIGIN(FLASH) > 8, " -The exception handlers are missing. This is likely a cortex-m-rt bug. -Please file a bug report at: -https://github.com/japaric/cortex-m-rt/issues"); - -ASSERT(_eexceptions - ORIGIN(FLASH) == 0x40, " -Invalid '.vector_table.exceptions' section. This is likely a -cortex-m-rt bug. Please file a bug report at: -https://github.com/japaric/cortex-m-rt/issues"); - -ASSERT(_einterrupts - _eexceptions > 0, " -The interrupt handlers are missing. If you are not linking to a device -crate then you supply the interrupt handlers yourself. Check the -documentation."); - -ASSERT(_einterrupts - _eexceptions <= 0x3c0, " -There can't be more than 240 interrupt handlers. This may be a bug in -your device crate, or you may have registered more than 240 interrupt -handlers."); - -ASSERT(_einterrupts <= _stext, " -The '.text' section can't be placed inside '.vector_table' section. -Set '_stext' to an address greater than '_einterrupts'"); - -ASSERT(_stext < ORIGIN(FLASH) + LENGTH(FLASH), " -The '.text' section must be placed inside the FLASH memory -Set '_stext' to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)"); - -ASSERT(_sgot == _egot, " -.got section detected in the input files. Dynamic relocations are not -supported. If you are linking to C code compiled using the `gcc` crate -then modify your build script to compile the C code _without_ the --fPIC flag. See the documentation of the `gcc::Config.fpic` method for -details."); diff --git a/link.x.in b/link.x.in new file mode 100644 index 00000000..9b99427f --- /dev/null +++ b/link.x.in @@ -0,0 +1,204 @@ +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut __sbss }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol if not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all 4-byte aligned. These alignments are assumed by the RAM initialization + routine. There's also a second benefit: 4-byte aligned boundaries means that you won't see + "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +/* Provides information about the memory layout of the device */ +/* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ +INCLUDE memory.x + +/* # Entry point = reset vector */ +ENTRY(Reset); +EXTERN(__RESET_VECTOR); /* depends on the `Reset` symbol */ + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFault); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # User overridable symbols I */ +/* Lets the user place the stack in a different RAM region */ +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); + +/* # Sections */ +SECTIONS +{ + /* ## Sections in FLASH */ + /* ### Vector table */ + .vector_table ORIGIN(FLASH) : ALIGN(4) + { + /* Initial Stack Pointer (SP) value */ + __STACK_START = .; /* Just to get a nicer name in the disassembly */ + LONG(_stack_start); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is `__RESET_VECTOR` symbol */ + __reset_vector = ABSOLUTE(.); + + /* Exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is `__EXCEPTIONS` symbol */ + __eexceptions = ABSOLUTE(.); + + /* Device specific interrupts */ + KEEP(*(.vector_table.interrupts)); /* this is `__INTERRUPTS` symbol */ + __einterrupts = ABSOLUTE(.); + } > FLASH + + /* ### .text */ + .text _stext : + { + *(.text .text.*); + __etext = ABSOLUTE(.); + } > FLASH + + /* ### .rodata */ + .rodata : + { + . = ALIGN(4); /* 4-byte align the start (VMA) of this section */ + /* __srodata = ABSOLUTE(.); */ + + *(.rodata .rodata.*); + + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + __erodata = ABSOLUTE(.); + } > FLASH + + /* ## Sections in RAM */ + /* ### .data */ + .data : AT(__erodata) /* LMA */ + { + . = ALIGN(4); /* 4-byte align the start (VMA) of this section */ + __sdata = ABSOLUTE(.); + + *(.data .data.*); + + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + __edata = ABSOLUTE(.); + } > RAM + + /* ### .bss */ + .bss : + { + . = ALIGN(4); /* 4-byte align the start (VMA) of this section */ + __sbss = ABSOLUTE(.); + + *(.bss .bss.*); + + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + __ebss = ABSOLUTE(.); + } > RAM + + /* ## Fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in + the input files and raise an error if relocatable code is found */ + .got : + { + __sgot = ABSOLUTE(.); + KEEP(*(.got .got.*)); + __egot = ABSOLUTE(.); + } > FLASH + + /* ## Discarded sections */ + /DISCARD/ : + { + /* Unused exception related info that only wastes space */ + *(.ARM.exidx.*); + } +} + +/* # User overridable symbols II */ +/* (The user overridable symbols are split in two parts because LLD demands that the RHS of PROVIDE + to be defined before the PROVIDE invocation) */ +/* Lets the user override this to place .text a bit further than the vector table. Required by +microcontrollers that store their configuration right after the vector table. */ +PROVIDE(_stext = __einterrupts); + +/* # Hardcoded symbols */ +/* Place `.bss` at the start of the RAM region */ +__sidata = LOADADDR(.data); +/* Place the heap right after `.bss` and `.data` */ +__sheap = __ebss; + +/* # Sanity checks */ + +/* Do not exceed this mark in the error messages below | */ +ASSERT(__reset_vector == ORIGIN(FLASH) + 0x8, " +cortex-m-rt: The reset vector is missing. This is a bug in cortex-m-rt. Please file a bug +report at: https://github.com/japaric/cortex-m-rt/issues"); + +ASSERT(__eexceptions - ORIGIN(FLASH) == 0x40, " +cortex-m-rt: The exception vectors are missing. This is a bug in cortex-m-rt. Please file +a bug report at: https://github.com/japaric/cortex-m-rt/issues"); + +ASSERT(__sheap >= __ebss, " +cortex-m-rt: The heap overlaps with the .bss section. This is a bug in cortex-m-rt. Please +file a bug report at: https://github.com/japaric/cortex-m-rt/issues"); + +ASSERT(__sheap >= __edata, " +cortex-m-rt: The heap overlaps with the .data section. This is a bug in cortex-m-rt. +Please file a bug report at: https://github.com/japaric/cortex-m-rt/issues"); + +ASSERT(__einterrupts - __eexceptions > 0, " +cortex-m-rt: The interrupt vectors are missing. Possible solutions, from most likely to +less likely: +- Link to a device crate +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency + may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); + +ASSERT(__einterrupts <= _stext, " +cortex-m-rt: The '.text' section can't be placed inside the '.vector_table' section. Set +'_stext' to an address greater than '__einterrupts' (cf. `nm` output)"); + +ASSERT(_stext < ORIGIN(FLASH) + LENGTH(FLASH), " +cortex-m-rt The '.text' section must be placed inside the FLASH memory. Set '_stext' to an +address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)"); + +/* This has been temporarily omitted because it's not supported by LLD */ +/* ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " */ +/* .bss is not 4-byte aligned at its boundaries. This is a cortex-m-rt bug."); */ + +/* ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " */ +/* .data is not 4-byte aligned at its boundaries. This is a cortex-m-rt bug."); */ + +/* ASSERT(__sidata % 4 == 0, " */ +/* __sidata is not 4-byte aligned. This is a cortex-m-rt bug."); */ + +ASSERT(__sgot == __egot, " +.got section detected in the input object files. Dynamic relocations are not supported. +If you are linking to C code compiled using the `cc` crate then modify your build script +to compile the C code _without_ the -fPIC flag. See the documentation of the +`cc::Build.pic` method for details."); +/* Do not exceed this mark in the error messages above | */ diff --git a/memory.x b/memory.x new file mode 100644 index 00000000..3d974148 --- /dev/null +++ b/memory.x @@ -0,0 +1,19 @@ +/* Device specific memory layout */ + +MEMORY +{ + /* FLASH and RAM are mandatory memory regions */ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K + + /* More memory regions can declared: for example this is a second RAM region */ + /* CCRAM : ORIGIN = 0x10000000, LENGTH = 8K */ +} + +/* The location of the stack can be overridden using the `_stack_start` symbol. + By default it will be placed at the end of the RAM region */ +/* _stack_start = ORIGIN(CCRAM) + LENGTH(CCRAM); */ + +/* The location of the .text section can be overridden using the `_stext` symbol. + By default it will place after .vector_table */ +/* _stext = ORIGIN(FLASH) + 0x40c; */ diff --git a/src/lang_items.rs b/src/lang_items.rs deleted file mode 100644 index 571a8a50..00000000 --- a/src/lang_items.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Lang item required to make the normal `main` work in applications -// -// This is how the `start` lang item works: -// When `rustc` compiles a binary crate, it creates a `main` function that looks -// like this: -// -// ``` -// #[export_name = "main"] -// pub extern "C" fn rustc_main(argc: isize, argv: *const *const u8) -> isize { -// start(main, argc, argv) -// } -// ``` -// -// Where `start` is this function and `main` is the binary crate's `main` -// function. -// -// The final piece is that the entry point of our program, the reset handler, -// has to call `rustc_main`. That's covered by the `reset_handler` function in -// root of this crate. -#[lang = "start"] -extern "C" fn start(main: fn() -> T, _argc: isize, _argv: *const *const u8) -> isize -where - T: Termination, -{ - main(); - - 0 -} - -#[lang = "termination"] -pub trait Termination { - fn report(self) -> i32; -} - -impl Termination for () { - fn report(self) -> i32 { - 0 - } -} diff --git a/src/lib.rs b/src/lib.rs index 7028cce5..eaefbbc8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,615 +1,854 @@ //! Minimal startup / runtime for Cortex-M microcontrollers //! +//! This crate contains all the required parts to build a `no_std` application (binary crate) that +//! targets a Cortex-M microcontroller. +//! //! # Features //! -//! This crate provides +//! This crates takes care of: +//! +//! - The memory layout of the program. In particular, it populates the vector table so the device +//! can boot correctly, and properly dispatch exceptions and interrupts. +//! +//! - Initializing `static` variables before the program entry point. +//! +//! - Enabling the FPU before the program entry point if the target is `thumbv7em-none-eabihf`. //! -//! - Before main initialization of the `.bss` and `.data` sections. +//! This crate also provides a mechanism to set exception handlers: see the [`exception!`] macro. //! -//! - Before main initialization of the FPU (for targets that have a FPU). +//! [`exception!`]: macro.exception.html //! -//! - A minimal `start` lang item to support the standard `fn main()` -//! interface. (The processor goes to sleep (`loop { asm!("wfi") }`) after -//! returning from `main`) +//! # Requirements //! -//! - A linker script that encodes the memory layout of a generic Cortex-M -//! microcontroller. This linker script is missing some information that must -//! be supplied through a `memory.x` file (see example below). +//! ## `arm-none-eabi-gcc` //! -//! - A default exception handler tailored for debugging that lets you inspect -//! what was the state of the processor at the time of the exception. By -//! default, all exceptions are serviced by this handler but each exception -//! can be individually overridden using the -//! [`exception!`](macro.exception.html) macro. The default exception handler -//! itself can also be overridden using the -//! [`default_handler!`](macro.default_handler.html) macro. +//! This crate requires `arm-none-eabi-gcc` to be installed and available in `$PATH`. //! -//! - A `_sheap` symbol at whose address you can locate a heap. +//! ## `memory.x` +//! +//! This crate expects the user, or some other crate, to provide the memory layout of the target +//! device via a linker script named `memory.x`. This section covers the contents of `memory.x` //! -//! # Example +//! ### `MEMORY` //! -//! Creating a new bare metal project. (I recommend you use the -//! [`cortex-m-quickstart`](https://docs.rs/cortex-m-quickstart/0.2.0/cortex_m_quickstart/) template -//! as it takes of all the boilerplate shown here) +//! The linker script must specify the memory available in the device as, at least, two `MEMORY` +//! regions: one named `FLASH` and one named `RAM`. The `.text` and `.rodata` sections of the +//! program will be placed in the `FLASH` region, whereas the `.bss` and `.data` sections, as well +//! as the heap,will be placed in the `RAM` region. //! //! ``` text -//! $ cargo new --bin app && cd $_ +//! /* Linker script for the STM32F103C8T6 */ +//! MEMORY +//! { +//! FLASH : ORIGIN = 0x08000000, LENGTH = 64K +//! RAM : ORIGIN = 0x20000000, LENGTH = 20K +//! } +//! ``` //! -//! $ # add this crate as a dependency -//! $ cargo add cortex-m-rt --vers 0.4.0 +//! ### `_stack_start` //! -//! $ # select a panicking behavior (look for the panic-impl keyword on crates.io) -//! $ cargo add panic-abort +//! This optional symbol can be used to indicate where the call stack of the program should be +//! placed. If this symbol is not used then the stack will be placed at the *end* of the `RAM` +//! region -- the stack grows downwards towards smaller address. This symbol can be used to place +//! the stack in a different memory region, for example: //! -//! $ # memory layout of the device -//! $ $EDITOR memory.x && cat $_ +//! ``` text +//! /* Linker script for the STM32F303VCT6 */ //! MEMORY //! { -//! /* NOTE K = KiBi = 1024 bytes */ -//! FLASH : ORIGIN = 0x08000000, LENGTH = 128K -//! RAM : ORIGIN = 0x20000000, LENGTH = 8K +//! FLASH : ORIGIN = 0x08000000, LENGTH = 256K +//! +//! /* .bss, .data and the heap go in this region */ +//! RAM : ORIGIN = 0x20000000, LENGTH = 40K +//! +//! /* Core coupled (faster) RAM dedicated to hold the stack */ +//! CCRAM : ORIGIN = 0x10000000, LENGTH = 8K //! } //! -//! $ $EDITOR src/main.rs && cat $_ +//! _stack_start = ORIGIN(CCRAM) + LENGTH(CCRAM); +//! ``` +//! +//! ### `_stext` +//! +//! This optional symbol can be used to control where the `.text` section is placed. If omitted the +//! `.text` section will be placed right after the vector table, which is placed at the beginning of +//! `FLASH`. Some devices store settings like Flash configuration right after the vector table; +//! for these devices one must place the `.text` section after this configuration section -- +//! `_stext` can be used for this purpose. +//! +//! ``` text +//! MEMORY +//! { +//! /* .. */ +//! } +//! +//! /* The device stores Flash configuration in 0x400-0x40C so we place .text after that */ +//! _stext = ORIGIN(FLASH) + 0x40C //! ``` //! -//! ``` ignore,no_run -//! #![feature(used)] +//! # An example +//! +//! This section presents a minimal application built on top of `cortex-m-rt`. Apart from the +//! mandatory `memory.x` linker script describing the memory layout of the device, the hard fault +//! handler and the default exception handler must also be defined somewhere in the dependency +//! graph (cf. [`exception!`]). In this example we define them in the binary crate: +//! +//! ``` ignore +//! // IMPORTANT the standard `main` interface is not used because it requires nightly +//! #![no_main] //! #![no_std] //! -//! extern crate cortex_m_rt; -//! extern crate panic_abort; // panicking behavior +//! #[macro_use(entry, exception)] +//! extern crate cortex_m_rt as rt; //! -//! fn main() { -//! // do something here +//! // makes `panic!` print messages to the host stderr using semihosting +//! extern crate panic_semihosting; +//! +//! use rt::ExceptionFrame; +//! +//! // use `main` as the entry point of this application +//! entry!(main); +//! +//! // `main` is not allowed to return +//! fn main() -> ! { +//! // initialization +//! +//! loop { +//! // application logic +//! } //! } //! -//! // As we are not using interrupts, we just register a dummy catch all -//! // handler -//! #[link_section = ".vector_table.interrupts"] -//! #[used] -//! static INTERRUPTS: [extern "C" fn(); 240] = [default_handler; 240]; +//! // define the hard fault handler +//! exception!(HardFault, hard_fault); //! -//! extern "C" fn default_handler() { -//! loop {} +//! fn hard_fault(ef: &ExceptionFrame) -> ! { +//! panic!("{:#?}", ef); +//! } +//! +//! // define the default exception handler +//! exception!(*, default_handler); +//! +//! fn default_handler(irqn: i16) { +//! panic!("unhandled exception (IRQn={})", irqn); //! } //! ``` //! +//! To actually build this program you need to place a `memory.x` linker script somewhere the linker +//! can find it, e.g. in the current directory; and then link the program using `cortex-m-rt`'s +//! linker script: `link.x`. The required steps are shown below: +//! //! ``` text -//! $ rustup target add thumbv7m-none-eabi +//! $ cat > memory.x <: -//! 8000400: b580 push {r7, lr} -//! 8000402: 466f mov r7, sp -//! 8000404: b084 sub sp, #8 +//! ## `device` //! +//! If this feature is disabled then this crate populates the whole vector table. All the interrupts +//! in the vector table, even the ones unused by the target device, will be bound to the default +//! exception handler. This makes the final application device agnostic: you will be able to run it +//! on any Cortex-M device -- provided that you correctly specified its memory layout in `memory.x` +//! -- without hitting undefined behavior. //! -//! $ arm-none-eabi-size -Ax $(find target -name app) | head -//! target/thumbv7m-none-eabi/debug/app : -//! section size addr -//! .vector_table 0x400 0x8000000 -//! .text 0x24a 0x8000400 -//! .rodata 0x0 0x800064c -//! .bss 0x0 0x20000000 -//! .data 0x0 0x20000000 -//! ``` +//! If this feature is enabled then the interrupts section of the vector table is left unpopulated +//! and some other crate, or the user, will have to populate it. This mode is meant to be used in +//! conjunction with crates generated using `svd2rust`. Those *device crates* will populate the +//! missing part of the vector table when their `"rt"` feature is enabled. //! -//! # Symbol interfaces +//! # Inspection //! -//! This crate makes heavy use of symbols, linker sections and linker scripts to -//! provide most of its functionality. Below are described the main symbol -//! interfaces. +//! This section covers how to inspect a binary that builds on top of `cortex-m-rt`. //! -//! ## `DEFAULT_HANDLER` +//! ## Sections (`size`) //! -//! This weak symbol can be overridden to override the default exception handler -//! that this crate provides. It's recommended that you use the -//! `default_handler!` to do the override, but below is shown how to manually -//! override the symbol: +//! `cortex-m-rt` uses standard sections like `.text`, `.rodata`, `.bss` and `.data` as one would +//! expect. `cortex-m-rt` separates the vector table in its own section, named `.vector_table`. This +//! lets you distinguish how much space is taking the vector table in Flash vs how much is being +//! used by actual instructions (`.text`) and constants (`.rodata`). //! -//! ``` ignore,no_run -//! #[no_mangle] -//! pub extern "C" fn DEFAULT_HANDLER() { -//! // do something here -//! } +//! ``` +//! $ size -Ax target/thumbv7m-none-eabi/examples/app +//! target/thumbv7m-none-eabi/release/examples/app : +//! section size addr +//! .vector_table 0x400 0x8000000 +//! .text 0x88 0x8000400 +//! .rodata 0x0 0x8000488 +//! .data 0x0 0x20000000 +//! .bss 0x0 0x20000000 //! ``` //! -//! ## `.vector_table.interrupts` +//! Without the `-A` argument `size` reports the sum of the sizes of `.text`, `.rodata` and +//! `.vector_table` under "text". //! -//! This linker section is used to register interrupt handlers in the vector -//! table. The recommended way to use this section is to populate it, once, with -//! an array of *weak* functions that just call the `DEFAULT_HANDLER` symbol. -//! Then the user can override them by name. +//! ``` +//! $ size target/thumbv7m-none-eabi/examples/app +//! text data bss dec hex filename +//! 1160 0 0 1660 67c target/thumbv7m-none-eabi/release/app +//! ``` //! -//! ### Example +//! ## Symbols (`objdump`, `nm`) //! -//! Populating the vector table +//! One will always find the following (unmangled) symbols in `cortex-m-rt` applications: //! -//! ``` ignore,no_run -//! // Number of interrupts the device has -//! const N: usize = 60; +//! - `Reset`. This is the reset handler. The microcontroller will executed this function upon +//! booting. This function will call the user program entry point (cf. [`entry!`]) using the `main` +//! symbol so you may also find that symbol in your program; if you do, `main` will contain your +//! application code. Some other times `main` gets inlined into `Reset` so you won't find it. //! -//! // Default interrupt handler that just calls the `DEFAULT_HANDLER` -//! #[linkage = "weak"] -//! #[naked] -//! #[no_mangle] -//! extern "C" fn WWDG() { -//! unsafe { -//! asm!("b DEFAULT_HANDLER" :::: "volatile"); -//! core::intrinsics::unreachable(); -//! } -//! } +//! [`entry!`]: macro.entry.html //! -//! // You need one function per interrupt handler -//! #[linkage = "weak"] -//! #[naked] -//! #[no_mangle] -//! extern "C" fn PVD() { -//! unsafe { -//! asm!("b DEFAULT_HANDLER" :::: "volatile"); -//! core::intrinsics::unreachable(); -//! } -//! } +//! - `DefaultHandler`. This is the default handler. This function will contain, or call, the +//! function you declared in the second argument of `exception!(*, ..)`. //! -//! // .. +//! - `HardFault`. This is the hard fault handler. This function is simply a trampoline that jumps +//! into the user defined hard fault handler: `UserHardFault`. The trampoline is required to set up +//! the pointer to the stacked exception frame. //! -//! // Use `None` for reserved spots in the vector table -//! #[link_section = ".vector_table.interrupts"] -//! #[no_mangle] -//! #[used] -//! static INTERRUPTS: [Option; N] = [ -//! Some(WWDG), -//! Some(PVD), -//! // .. -//! ]; -//! ``` +//! - `UserHardFault`. This is the user defined hard fault handler. This function will contain, or +//! call, the function you declared in the second argument of `exception!(HardFault, ..)` //! -//! Overriding an interrupt (this can be in a different crate) +//! - `__STACK_START`. This is the first entry in the `.vector_table` section. This symbol contains +//! the initial value of the stack pointer; this is where the stack will be located -- the stack +//! grows downwards towards smaller addresses. //! -//! ``` ignore,no_run -//! // the name must match the name of one of the weak functions used to -//! // populate the vector table. -//! #[no_mangle] -//! pub extern "C" fn WWDG() { -//! // do something here -//! } -//! ``` +//! - `__RESET_VECTOR`. This is the reset vector, a pointer into the `Reset` handler. This vector is +//! located in the `.vector_table` section after `__STACK_START`. //! -//! ## `memory.x` +//! - `__EXCEPTIONS`. This is the core exceptions portion of the vector table; it's an array of 14 +//! exception vectors, which includes exceptions like `HardFault` and `SysTick`. This array is +//! located after `__RESET_VECTOR` in the `.vector_table` section. //! -//! This file supplies the information about the device to the linker. +//! - `__EXCEPTIONS`. This is the device specific interrupt portion of the vector table; its exact +//! size depends on the target device but if the `"device"` feature has not been enabled it will +//! have a size of 32 vectors (on ARMv6-M) or 240 vectors (on ARMv7-M). This array is located after +//! `__EXCEPTIONS` in the `.vector_table` section. //! -//! ### `MEMORY` +//! If you override any exception handler you'll find it as an unmangled symbol, e.g. `SysTick` or +//! `SVCall`, in the output of `objdump`, //! -//! The main information that this file must provide is the memory layout of -//! the device in the form of the `MEMORY` command. The command is documented -//! [here](https://sourceware.org/binutils/docs/ld/MEMORY.html), but at a minimum you'll want to -//! create two memory regions: one for Flash memory and another for RAM. +//! If you are targeting the `thumbv7em-none-eabihf` target you'll also see a `ResetTrampoline` +//! symbol in the output. To avoid the compiler placing FPU instructions before the FPU has been +//! enabled (cf. `vpush`) `Reset` calls the function `ResetTrampoline` which is marked as +//! `#[inline(never)]` and `ResetTrampoline` calls `main`. The compiler is free to inline `main` +//! into `ResetTrampoline` but it can't inline `ResetTrampoline` into `Reset` -- the FPU is enabled +//! in `Reset`. //! -//! The program instructions (the `.text` section) will be stored in the memory -//! region named FLASH, and the program `static` variables (the sections `.bss` -//! and `.data`) will be allocated in the memory region named RAM. +//! # Advanced usage //! -//! ### `_stack_start` +//! ## Setting the program entry point //! -//! This symbol provides the address at which the call stack will be allocated. -//! The call stack grows downwards so this address is usually set to the highest -//! valid RAM address plus one (this *is* an invalid address but the processor -//! will decrement the stack pointer *before* using its value as an address). +//! This section describes how `entry!` is implemented. This information is useful to developers who +//! want to provide an alternative to `entry!` that provides extra guarantees. //! -//! If omitted this symbol value will default to `ORIGIN(RAM) + LENGTH(RAM)`. +//! The `Reset` handler will call a symbol named `main` (unmangled) *after* initializing `.bss` and +//! `.data`, and enabling the FPU (if the target is `thumbv7em-none-eabihf`). `entry!` provides this +//! symbol in its expansion: //! -//! #### Example +//! ``` ignore +//! entry!(path::to::main); //! -//! Allocating the call stack on a different RAM region. +//! // expands into //! -//! ``` ignore -//! MEMORY -//! { -//! /* call stack will go here */ -//! CCRAM : ORIGIN = 0x10000000, LENGTH = 8K -//! FLASH : ORIGIN = 0x08000000, LENGTH = 256K -//! /* static variables will go here */ -//! RAM : ORIGIN = 0x20000000, LENGTH = 40K -//! } +//! #[export_name = "main"] +//! pub extern "C" fn __impl_main() -> ! { +//! // validate the signature of the program entry point +//! let f: fn() -> ! = path::to::main; //! -//! _stack_start = ORIGIN(CCRAM) + LENGTH(CCRAM); +//! f() +//! } //! ``` //! -//! ### `_stext` +//! The unmangled `main` symbol must have signature `extern "C" fn() -> !` or its invocation from +//! `Reset` will result in undefined behavior. //! -//! This symbol indicates where the `.text` section will be located. If not -//! specified in the `memory.x` file it will default to right after the vector -//! table -- the vector table is always located at the start of the FLASH -//! region. +//! ## Incorporating device specific interrupts //! -//! The main use of this symbol is leaving some space between the vector table -//! and the `.text` section unused. This is required on some microcontrollers -//! that store some configuration information right after the vector table. +//! This section covers how an external crate can insert device specific interrupt handlers into the +//! vector table. Most users don't need to concern themselves with these details, but if you are +//! interested in how device crates generated using `svd2rust` integrate with `cortex-m-rt` read on. //! -//! #### Example +//! The information in this section applies when the `"device"` feature has been enabled. //! -//! Locate the `.text` section 1024 bytes after the start of the FLASH region. +//! ### `__INTERRUPTS` //! -//! ``` ignore -//! _stext = ORIGIN(FLASH) + 0x400; -//! ``` +//! The external crate must provide the interrupts portion of the vector table via a `static` +//! variable named`__INTERRUPTS` (unmangled) that must be placed in the `.vector_table.interrupts` +//! section of its object file. //! -//! ### `_sheap` +//! This `static` variable will be placed at `ORIGIN(FLASH) + 0x40`. This address corresponds to the +//! spot where IRQ0 (IRQ number 0) is located. //! -//! This symbol is located in RAM right after the `.bss` and `.data` sections. -//! You can use the address of this symbol as the start address of a heap -//! region. This symbol is 4 byte aligned so that address will be a multiple of 4. +//! To conform to the Cortex-M ABI `__INTERRUPTS` must be an array of function pointers; some spots +//! in this array may need to be set to 0 if they are marked as *reserved* in the data sheet / +//! reference manual. We recommend using a `union` to set the reserved spots to `0`; `None` +//! (`Option`) may also work but it's not guaranteed that the `None` variant will *always* be +//! represented by the value `0`. //! -//! #### Example +//! Let's illustrate with an artificial example where a device only has two interrupt: `Foo`, with +//! IRQ number = 2, and `Bar`, with IRQ number = 4. //! //! ``` ignore -//! extern crate some_allocator; -//! -//! // Size of the heap in bytes -//! const SIZE: usize = 1024; +//! union Vector { +//! handler: extern "C" fn(), +//! reserved: usize, +//! } //! //! extern "C" { -//! static mut _sheap: u8; +//! fn Foo(); +//! fn Bar(); //! } //! +//! #[link_section = ".vector_table.interrupts"] +//! #[no_mangle] +//! pub static __INTERRUPTS: [Vector; 5] = [ +//! // 0-1: Reserved +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! +//! // 2: Foo +//! Vector { handler: Foo }, +//! +//! // 3: Reserved +//! Vector { reserved: 0 }, +//! +//! // 4: Bar +//! Vector { handler: Bar }, +//! ]; +//! ``` +//! +//! ### `device.x` +//! +//! Linking in `__INTERRUPTS` creates a bunch of undefined references. If the user doesn't set a +//! handler for *all* the device specific interrupts then linking will fail with `"undefined +//! reference"` errors. +//! +//! We want to provide a default handler for all the interrupts while still letting the user +//! individually override each interrupt handler. In C projects, this is usually accomplished using +//! weak aliases declared in external assembly files. In Rust, we could achieve something similar +//! using `global_asm!`, but that's an unstable feature. +//! +//! A solution that doesn't require `global_asm!` or external assembly files is to use the `PROVIDE` +//! command in a linker script to create the weak aliases. This is the approach that `cortex-m-rt` +//! uses; when the `"device"` feature is enabled `cortex-m-rt`'s linker script (`link.x`) depends on +//! a linker script named `device.x`. The crate that provides `__INTERRUPTS` must also provide this +//! file. +//! +//! For our running example the `device.x` linker script looks like this: +//! +//! ``` text +//! /* device.x */ +//! PROVIDE(Foo = DefaultHandler); +//! PROVIDE(Bar = DefaultHandler); +//! ``` +//! +//! This weakly aliases both `Foo` and `Bar`. `DefaultHandler` is the default exception handler that +//! the user provides via `exception!(*, ..)` and that the core exceptions use unless overridden. +//! +//! Because this linker script is provided by a dependency of the final application the dependency +//! must contain build script that puts `device.x` somewhere the linker can find. An example of such +//! build script is shown below: +//! +//! ``` ignore +//! use std::env; +//! use std::fs::File; +//! use std::io::Write; +//! use std::path::PathBuf; +//! //! fn main() { -//! unsafe { -//! let start_address = &mut _sheap as *mut u8; -//! some_allocator::initialize(start_address, SIZE); -//! } +//! // Put the linker script somewhere the linker can find it +//! let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); +//! File::create(out.join("device.x")) +//! .unwrap() +//! .write_all(include_bytes!("device.x")) +//! .unwrap(); +//! println!("cargo:rustc-link-search={}", out.display()); //! } //! ``` -//! -//! [1]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html -//! [qs]: https://docs.rs/cortex-m-quickstart/0.2.0/cortex_m_quickstart/ -//! [2]: https://sourceware.org/binutils/docs/ld/MEMORY.html + +// # Developer notes +// +// - `link_section` is used to place symbols in specific places of the final binary. The names used +// here will appear in the linker script (`link.x`) in conjunction with the `KEEP` command. #![deny(missing_docs)] #![deny(warnings)] -#![feature(asm)] -#![feature(core_intrinsics)] -#![feature(global_asm)] -#![feature(lang_items)] -#![feature(linkage)] -#![feature(naked_functions)] -#![feature(used)] #![no_std] -#[cfg(target_arch = "arm")] -extern crate cortex_m; -#[cfg(target_arch = "arm")] extern crate r0; -#[cfg(not(test))] -mod lang_items; +use core::fmt; -#[cfg(target_arch = "arm")] -use core::intrinsics; +/// Registers stacked (pushed into the stack) during an exception +#[derive(Clone, Copy)] +#[repr(C)] +pub struct ExceptionFrame { + /// (General purpose) Register 0 + pub r0: u32, -#[cfg(target_arch = "arm")] -use cortex_m::asm; -#[cfg(target_arch = "arm")] -use cortex_m::exception::ExceptionFrame; + /// (General purpose) Register 1 + pub r1: u32, -extern "C" { - // NOTE `rustc` forces this signature on us. See `src/lang_items.rs` - #[cfg(target_arch = "arm")] - fn main(argc: isize, argv: *const *const u8) -> isize; + /// (General purpose) Register 2 + pub r2: u32, + + /// (General purpose) Register 3 + pub r3: u32, - // Boundaries of the .bss section - static mut _ebss: u32; - static mut _sbss: u32; + /// (General purpose) Register 12 + pub r12: u32, - // Boundaries of the .data section - static mut _edata: u32; - static mut _sdata: u32; + /// Linker Register + pub lr: u32, - // Initial values of the .data section (stored in Flash) - static _sidata: u32; + /// Program Counter + pub pc: u32, + + /// Program Status Register + pub xpsr: u32, } -#[cfg(target_arch = "arm")] -#[link_section = ".vector_table.reset_vector"] -#[used] -static RESET_VECTOR: unsafe extern "C" fn() -> ! = reset_handler; +impl fmt::Debug for ExceptionFrame { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + struct Hex(u32); + impl fmt::Debug for Hex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "0x{:08x}", self.0) + } + } + f.debug_struct("ExceptionFrame") + .field("r0", &Hex(self.r0)) + .field("r1", &Hex(self.r1)) + .field("r2", &Hex(self.r2)) + .field("r3", &Hex(self.r3)) + .field("r12", &Hex(self.r12)) + .field("lr", &Hex(self.lr)) + .field("pc", &Hex(self.pc)) + .field("xpsr", &Hex(self.xpsr)) + .finish() + } +} -/// The reset handler +/// Returns a pointer to the start of the heap /// -/// This is the entry point of all programs -#[cfg(target_arch = "arm")] -#[link_section = ".reset_handler"] -unsafe extern "C" fn reset_handler() -> ! { - r0::zero_bss(&mut _sbss, &mut _ebss); - r0::init_data(&mut _sdata, &mut _edata, &_sidata); +/// The returned pointer is guaranteed to be 4-byte aligned. +#[inline] +pub fn heap_start() -> *mut u32 { + extern "C" { + static mut __sheap: u32; + } + + unsafe { &mut __sheap } +} + +/* Entry point */ +#[doc(hidden)] +#[link_section = ".vector_table.reset_vector"] +#[no_mangle] +pub static __RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; + +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn Reset() -> ! { + extern "C" { + // This symbol will be provided by the user via the `entry!` macro + fn main() -> !; + + // These symbols come from `link.x` + static mut __sbss: u32; + static mut __ebss: u32; + + static mut __sdata: u32; + static mut __edata: u32; + static __sidata: u32; + } + + // Initialize RAM + r0::zero_bss(&mut __sbss, &mut __ebss); + r0::init_data(&mut __sdata, &mut __edata, &__sidata); match () { #[cfg(not(has_fpu))] - () => { - // Neither `argc` or `argv` make sense in bare metal context so we - // just stub them - main(0, ::core::ptr::null()); - } + () => main(), #[cfg(has_fpu)] () => { - // NOTE(safe) no exception / interrupt that also accesses the FPU - // can occur here - let scb = &*cortex_m::peripheral::SCB.get(); - scb.enable_fpu(); - - // Make sure the user main function never gets inlined into this - // function as that may cause FPU related instructions like vpush to - // be executed *before* enabling the FPU and that would generate an - // exception + // We redefine these here to avoid pulling the `cortex-m` crate as a dependency + const SCB_CPACR: *mut u32 = 0xE000_ED88 as *mut u32; + const SCB_CPACR_FPU_ENABLE: u32 = 0b01_01 << 20; + const SCB_CPACR_FPU_USER: u32 = 0b10_10 << 20; + + // enable the FPU + core::ptr::write_volatile( + SCB_CPACR, + *SCB_CPACR | SCB_CPACR_FPU_ENABLE | SCB_CPACR_FPU_USER, + ); + + // this is used to prevent the compiler from inlining the user `main` into the reset + // handler. Inlining can cause the FPU instructions in the user `main` to be executed + // before enabling the FPU, and that would produce a hard to diagnose hard fault at + // runtime. #[inline(never)] - fn main() { - unsafe { - ::main(0, ::core::ptr::null()); - } + #[export_name = "ResetTrampoline"] + fn trampoline() -> ! { + unsafe { main() } } - main() + trampoline() } } +} - // If `main` returns, then we go into "reactive" mode and simply attend - // interrupts as they occur. - loop { - asm!("wfi" :::: "volatile"); - } +/// Macro to define the entry point of the program +/// +/// **NOTE** This macro must be invoked once and must be invoked from an accessible module, ideally +/// from the root of the crate. +/// +/// Usage: `entry!(path::to::entry::point)` +/// +/// The specified function will be called by the reset handler *after* RAM has been initialized. In +/// the case of the `thumbv7em-none-eabihf` target the FPU will also be enabled before the function +/// is called. +/// +/// The signature of the specified function must be `fn() -> !` (never ending function) +#[macro_export] +macro_rules! entry { + ($path:path) => { + #[export_name = "main"] + pub extern "C" fn __impl_main() -> ! { + // validate the signature of the program entry point + let f: fn() -> ! = $path; + + f() + } + }; } -#[cfg(target_arch = "arm")] -global_asm!( - r#" -.weak NMI -NMI = DEFAULT_HANDLER +/* Exceptions */ +#[doc(hidden)] +pub enum Exception { + NonMaskableInt, -.weak HARD_FAULT -HARD_FAULT = DEFAULT_HANDLER + // Not overridable + // HardFault, + #[cfg(not(armv6m))] + MemoryManagement, -.weak MEM_MANAGE -MEM_MANAGE = DEFAULT_HANDLER + #[cfg(not(armv6m))] + BusFault, -.weak BUS_FAULT -BUS_FAULT = DEFAULT_HANDLER + #[cfg(not(armv6m))] + UsageFault, -.weak USAGE_FAULT -USAGE_FAULT = DEFAULT_HANDLER + #[cfg(armv8m)] + SecureFault, -.weak SVCALL -SVCALL = DEFAULT_HANDLER + SVCall, -.weak PENDSV -PENDSV = DEFAULT_HANDLER + #[cfg(not(armv6m))] + DebugMonitor, -.weak SYS_TICK -SYS_TICK = DEFAULT_HANDLER -"# -); + PendSV, -#[cfg(not(armv6m))] -global_asm!( - r#" -.weak DEBUG_MONITOR -DEBUG_MONITOR = DEFAULT_HANDLER -"# -); + SysTick, +} -#[cfg(target_arch = "arm")] extern "C" { - fn NMI(); - fn HARD_FAULT(); - fn MEM_MANAGE(); - fn BUS_FAULT(); - fn USAGE_FAULT(); - fn SVCALL(); + fn NonMaskableInt(); + + fn HardFault(); + + #[cfg(not(armv6m))] + fn MemoryManagement(); + + #[cfg(not(armv6m))] + fn BusFault(); + + #[cfg(not(armv6m))] + fn UsageFault(); + + #[cfg(armv8m)] + fn SecureFault(); + + fn SVCall(); + #[cfg(not(armv6m))] - fn DEBUG_MONITOR(); - fn PENDSV(); - fn SYS_TICK(); + fn DebugMonitor(); + + fn PendSV(); + + fn SysTick(); +} + +#[doc(hidden)] +pub union Vector { + handler: unsafe extern "C" fn(), + reserved: usize, } -#[allow(private_no_mangle_statics)] -#[cfg(target_arch = "arm")] #[doc(hidden)] #[link_section = ".vector_table.exceptions"] #[no_mangle] -#[used] -pub static EXCEPTIONS: [Option; 14] = [ - Some(NMI), - Some(HARD_FAULT), - Some(MEM_MANAGE), - Some(BUS_FAULT), - Some(USAGE_FAULT), - None, - None, - None, - None, - Some(SVCALL), +pub static __EXCEPTIONS: [Vector; 14] = [ + // Exception 2: Non Maskable Interrupt. + Vector { + handler: NonMaskableInt, + }, + // Exception 3: Hard Fault Interrupt. + Vector { handler: HardFault }, + // Exception 4: Memory Management Interrupt [not on Cortex-M0 variants]. + #[cfg(not(armv6m))] + Vector { + handler: MemoryManagement, + }, + #[cfg(armv6m)] + Vector { reserved: 0 }, + // Exception 5: Bus Fault Interrupt [not on Cortex-M0 variants]. + #[cfg(not(armv6m))] + Vector { handler: BusFault }, #[cfg(armv6m)] - None, + Vector { reserved: 0 }, + // Exception 6: Usage Fault Interrupt [not on Cortex-M0 variants]. #[cfg(not(armv6m))] - Some(DEBUG_MONITOR), - None, - Some(PENDSV), - Some(SYS_TICK), + Vector { + handler: UsageFault, + }, + #[cfg(armv6m)] + Vector { reserved: 0 }, + // Exception 7: Secure Fault Interrupt [only on Armv8-M]. + #[cfg(armv8m)] + Vector { + handler: SecureFault, + }, + #[cfg(not(armv8m))] + Vector { reserved: 0 }, + // 8-10: Reserved + Vector { reserved: 0 }, + Vector { reserved: 0 }, + Vector { reserved: 0 }, + // Exception 11: SV Call Interrupt. + Vector { handler: SVCall }, + // Exception 12: Debug Monitor Interrupt [not on Cortex-M0 variants]. + #[cfg(not(armv6m))] + Vector { + handler: DebugMonitor, + }, + #[cfg(armv6m)] + Vector { reserved: 0 }, + // 13: Reserved + Vector { reserved: 0 }, + // Exception 14: Pend SV Interrupt [not on Cortex-M0 variants]. + Vector { handler: PendSV }, + // Exception 15: System Tick Interrupt. + Vector { handler: SysTick }, ]; -/// `ef` points to the exception frame -/// -/// That exception frame is a snapshot of the program state right before the -/// exception occurred. -#[allow(unused_variables)] -#[cfg(target_arch = "arm")] -extern "C" fn default_handler(ef: &ExceptionFrame) -> ! { - asm::bkpt(); - - loop {} - - #[export_name = "DEFAULT_HANDLER"] - #[linkage = "weak"] - #[naked] - extern "C" fn trampoline() -> ! { - unsafe { - asm!("mrs r0, MSP - b $0" - : - : "i"(default_handler as extern "C" fn(&ExceptionFrame) -> !) - : - : "volatile"); - - intrinsics::unreachable() - } +// If we are not targeting a specific device we bind all the potential device specific interrupts +// to the default handler +#[cfg(all(not(feature = "device"), not(armv6m)))] +#[doc(hidden)] +#[link_section = ".vector_table.interrupts"] +#[no_mangle] +pub static __INTERRUPTS: [unsafe extern "C" fn(); 240] = [{ + extern "C" { + fn DefaultHandler(); } - #[used] - static KEEP: extern "C" fn() -> ! = trampoline; -} + DefaultHandler +}; 240]; -// make sure the compiler emits the DEFAULT_HANDLER symbol so the linker can -// find it! -#[cfg(target_arch = "arm")] -#[used] -static KEEP: extern "C" fn(&ExceptionFrame) -> ! = default_handler; +// ARMv6-M can only have a maximum of 32 device specific interrupts +#[cfg(all(not(feature = "device"), armv6m))] +#[doc(hidden)] +#[link_section = ".vector_table.interrupts"] +#[no_mangle] +pub static __INTERRUPTS: [unsafe extern "C" fn(); 32] = [{ + extern "C" { + fn DefaultHandler(); + } + + DefaultHandler +}; 32]; -/// This macro lets you override the default exception handler +/// Macro to set or override a processor core exception handler /// -/// The first and only argument to this macro is the path to the function that -/// will be used as the default handler. That function must have signature -/// `fn()` +/// **NOTE** This macro must be invoked from an accessible module, ideally from the root of the +/// crate. /// -/// # Examples +/// # Syntax /// /// ``` ignore -/// default_handler!(foo::bar); +/// exception!( +/// // Name of the exception +/// $Name:ident, /// -/// mod foo { -/// pub fn bar() { -/// ::cortex_m::asm::bkpt(); -/// loop {} -/// } -/// } +/// // Path to the exception handler (a function) +/// $handler:path, +/// +/// // Optional, state preserved across invocations of the handler +/// state: $State:ty = $initial_state:expr, +/// ); /// ``` -#[macro_export] -macro_rules! default_handler { - ($path:path) => { - #[allow(non_snake_case)] - #[doc(hidden)] - #[no_mangle] - pub unsafe extern "C" fn DEFAULT_HANDLER() { - // type checking - let f: fn() = $path; - f(); - } - } -} - -/// Fault and system exceptions -#[allow(non_camel_case_types)] -#[doc(hidden)] -pub enum Exception { - /// Non-maskable interrupt - NMI, - /// All class of fault. - HARD_FAULT, - /// Memory management. - MEN_MANAGE, - /// Pre-fetch fault, memory access fault. - BUS_FAULT, - /// Undefined instruction or illegal state. - USAGE_FAULT, - /// System service call via SWI instruction - SVCALL, - /// Debug monitor - #[cfg(not(armv6m))] - DEBUG_MONITOR, - /// Pendable request for system service - PENDSV, - /// System tick timer - SYS_TICK, -} - -/// Assigns a handler to an exception /// -/// This macro takes two arguments: the name of an exception and the path to the -/// function that will be used as the handler of that exception. That function -/// must have signature `fn()`. +/// where `$Name` can be one of: /// -/// Optionally, a third argument may be used to declare exception local data. -/// The handler will have exclusive access to these *local* variables on each -/// invocation. If the third argument is used then the signature of the handler -/// function must be `fn(&mut $NAME::Locals)` where `$NAME` is the first -/// argument passed to the macro. +/// - `*` +/// - `NonMaskableInt` +/// - `HardFault` +/// - `MemoryManagement` (a) +/// - `BusFault` (a) +/// - `UsageFault` (a) +/// - `SecureFault` (b) +/// - `SVCall` +/// - `DebugMonitor` (a) +/// - `PendSV` +/// - `SysTick` /// -/// # Example +/// (a) Not available on Cortex-M0 variants (`thumbv6m-none-eabi`) /// -/// ``` ignore -/// exception!(MEM_MANAGE, mpu_fault); +/// (b) Only available on ARMv8-M +/// +/// # Usage +/// +/// `exception!(HardFault, ..)` sets the hard fault handler. The handler must have signature +/// `fn(&ExceptionFrame) -> !`. This handler is not allowed to return as that can cause undefined +/// behavior. It's mandatory to set the `HardFault` handler somewhere in the dependency graph of an +/// application. /// -/// fn mpu_fault() { -/// panic!("Oh no! Something went wrong"); +/// `exception!(*, ..)` sets the *default* handler. All exceptions which have not been assigned a +/// handler will be serviced by this handler. This handler must have signature `fn(irqn: i16)`. +/// `irqn` is the IRQ number (cf. CMSIS); `irqn` will be a negative number when the handler is +/// servicing a core exception; `irqn` will be a positive number when the handler is servicing a +/// device specific exception (interrupt). It's mandatory to set the default handler somewhere +/// in the dependency graph of an application. +/// +/// `exception!($Exception, ..)` overrides the default handler for `$Exception`. All exceptions, +/// except for `HardFault`, can be assigned some `$State`. +/// +/// # Examples +/// +/// - Setting the `HardFault` handler +/// +/// ``` +/// #[macro_use(exception)] +/// extern crate cortex_m_rt as rt; +/// +/// use rt::ExceptionFrame; +/// +/// exception!(HardFault, hard_fault); +/// +/// fn hard_fault(ef: &ExceptionFrame) -> ! { +/// // prints the exception frame as a panic message +/// panic!("{:#?}", ef); /// } /// -/// exception!(SYS_TICK, periodic, locals: { -/// counter: u32 = 0; -/// }); +/// # fn main() {} +/// ``` +/// +/// - Setting the default handler +/// +/// ``` +/// #[macro_use(exception)] +/// extern crate cortex_m_rt as rt; +/// +/// exception!(*, default_handler); /// -/// fn periodic(locals: &mut SYS_TICK::Locals) { -/// locals.counter += 1; -/// println!("This function has been called {} times", locals.counter); +/// fn default_handler(irqn: i16) { +/// println!("IRQn = {}", irqn); /// } +/// +/// # fn main() {} +/// ``` +/// +/// - Overriding the `SysTick` handler +/// +/// ``` +/// #[macro_use(exception)] +/// extern crate cortex_m_rt as rt; +/// +/// exception!(SysTick, sys_tick, state: u32 = 0); +/// +/// fn sys_tick(count: &mut u32) { +/// println!("count = {}", *count); +/// +/// *count += 1; +/// } +/// +/// # fn main() {} /// ``` #[macro_export] macro_rules! exception { - ($NAME:ident, $path:path, locals: { - $($lvar:ident:$lty:ident = $lval:expr;)+ - }) => { - #[allow(non_snake_case)] - mod $NAME { - pub struct Locals { - $( - pub $lvar: $lty, - )+ - } + (* , $handler:path) => { + #[allow(unsafe_code)] + #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible + #[no_mangle] + pub unsafe extern "C" fn DefaultHandler() { + extern crate core; + + // validate the signature of the user provided handler + let f: fn(i16) = $handler; + + const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; + + // NOTE not volatile so the compiler can opt the load operation away if the value is + // unused + f(core::ptr::read(SCB_ICSR) as u8 as i16 - 16) } + }; - #[allow(non_snake_case)] - #[doc(hidden)] + (HardFault, $handler:path) => { + #[allow(unsafe_code)] + #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible #[no_mangle] - pub unsafe extern "C" fn $NAME() { - // check that the handler exists - let _ = $crate::Exception::$NAME; - - static mut LOCALS: self::$NAME::Locals = self::$NAME::Locals { - $( - $lvar: $lval, - )* - }; - - // type checking - let f: fn(&mut self::$NAME::Locals) = $path; - f(&mut LOCALS); + pub unsafe extern "C" fn UserHardFault(ef: &$crate::ExceptionFrame) { + // validate the signature of the user provided handler + let f: fn(&$crate::ExceptionFrame) -> ! = $handler; + + f(ef) } }; - ($NAME:ident, $path:path) => { - #[allow(non_snake_case)] - #[doc(hidden)] + + ($Name:ident, $handler:path,state: $State:ty = $initial_state:expr) => { + #[allow(unsafe_code)] + #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible #[no_mangle] - pub unsafe extern "C" fn $NAME() { - // check that the handler exists - let _ = $crate::Exception::$NAME; + pub unsafe extern "C" fn $Name() { + static mut STATE: $State = $initial_state; - // type checking - let f: fn() = $path; - f(); + // check that this exception exists + let _ = $crate::Exception::$Name; + + // validate the signature of the user provided handler + let f: fn(&mut $State) = $handler; + + f(&mut STATE) } - } + }; + + ($Name:ident, $handler:path) => { + #[allow(unsafe_code)] + #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible + #[no_mangle] + pub unsafe extern "C" fn $Name() { + // check that this exception exists + let _ = $crate::Exception::$Name; + + // validate the signature of the user provided handler + let f: fn() = $handler; + + f() + } + }; }