From b01938ab3a5467fb6d741346c586961a09846a98 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 14 Nov 2018 23:12:59 +0100 Subject: [PATCH 1/6] bring up to par with cortex-m-rt v0.6.x - this brings in cortex-m-rt attributes: entry, pre_init and interrupt - it also removes almost all dependencies on unstable features -- only the msp430-interrupt ABI is left behind. - it also removes the abort-on-panic feature and the panic_handler (panic_impl lang item) --- .gitignore | 1 + Cargo.toml | 17 +- asm.s | 6 + assemble.sh | 15 ++ bin/msp430-none-elf.a | Bin 0 -> 928 bytes build.rs | 20 +- link.x | 84 +++---- macros/Cargo.toml | 22 ++ macros/src/lib.rs | 452 +++++++++++++++++++++++++++++++++ src/lang_items.rs | 64 ----- src/lib.rs | 571 +++++++++++++++++++++--------------------- 11 files changed, 843 insertions(+), 409 deletions(-) create mode 100644 asm.s create mode 100755 assemble.sh create mode 100644 bin/msp430-none-elf.a create mode 100644 macros/Cargo.toml create mode 100644 macros/src/lib.rs delete mode 100644 src/lang_items.rs diff --git a/.gitignore b/.gitignore index 6dc3db1..e64a511 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ **/*.rs.bk +.#* Cargo.lock target/ diff --git a/Cargo.toml b/Cargo.toml index dfae770..6393e01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,19 +6,26 @@ authors = [ categories = ["embedded", "no-std"] description = "Minimal runtime / startup for MSP430 microcontrollers" documentation = "https://docs.rs/msp430-rt" +edition = "2018" keywords = ["msp430", "runtime", "startup"] license = "MIT OR Apache-2.0" name = "msp430-rt" repository = "https://github.com/pftbest/msp430-rt" -version = "0.1.3" +version = "0.2.0" [dependencies] msp430 = "0.1.0" r0 = "0.2.2" +[dependencies.msp430-rt-macros] +version = "0.2.0" +path = "macros" + [features] -# provides a panic_fmt implementation that goes into infinite loop -abort-on-panic = [] +device = ["msp430-rt-macros/device"] + +[package.metadata.docs.rs] +features = ["device"] -[build-dependencies] -rustc_version = "0.2.1" +[workspace] +members = ["macros"] \ No newline at end of file diff --git a/asm.s b/asm.s new file mode 100644 index 0000000..5e2c79f --- /dev/null +++ b/asm.s @@ -0,0 +1,6 @@ + .section .ResetTrampoline, "ax" + .global ResetTrampoline + .type ResetTrampoline,%function +ResetTrampoline: + mov #_stack_start,r1 + br Reset diff --git a/assemble.sh b/assemble.sh new file mode 100755 index 0000000..3097d25 --- /dev/null +++ b/assemble.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -euxo pipefail + +# cflags taken from cc 1.0.22 + +crate=msp430-rt + +# remove existing blobs because otherwise this will append object files to the old blobs +rm -f bin/*.a + +msp430-elf-as -mcpu=msp430 asm.s -o bin/$crate.o +ar crs bin/msp430-none-elf.a bin/$crate.o + +rm bin/$crate.o diff --git a/bin/msp430-none-elf.a b/bin/msp430-none-elf.a new file mode 100644 index 0000000000000000000000000000000000000000..42973405e5e24035d27248151931ed970a21abe7 GIT binary patch literal 928 zcmah{!AiqG5S^GB+vuTsP(hFs1U=~5YG^M-s-PzY(Rvf?rUqJUZON`sPyT`*>3`^( zBrBUj>A>u~*?oDlJCj;lCjG&Me;O36ZlxKv*C03x4WZ`~kpq8zh*hk+i5yQSqv3l@ zV>J!W>nDj8lT?0@W;4vhTCHX-+Vj7zZ*LsO`Gsz34h_>HCZD0=I+8HmI3k}a)aVdZ z;g{RkL~$A68zz+;&nfd27n@s9Cvs_}Kz6ZXn@c4-#i>_N*uwdy#i#=B&*e1VuotCe z|Bcr~XNT!6R@br*J%my@7ZK`ntYt((=_HE-gLFL8BwnSKBqF6qB=Ja!`C{Vk@gCz5 zQtMC3nFv!j5 Q@xNj70vtRU5~AV#0nH&(EC2ui literal 0 HcmV?d00001 diff --git a/build.rs b/build.rs index 37c2de5..dcb40ff 100644 --- a/build.rs +++ b/build.rs @@ -1,20 +1,20 @@ -extern crate rustc_version; - -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; +use std::{env, fs, fs::File, io::Write, path::PathBuf}; fn main() { - println!("cargo:rustc-cfg=has_termination_lang"); + let target = env::var("TARGET").unwrap(); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + if target == "msp430-none-elf" { + fs::copy(format!("bin/{}.a", target), out_dir.join("libmsp430-rt.a")).unwrap(); + println!("cargo:rustc-link-lib=static=msp430-rt"); + } // 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")) + File::create(out_dir.join("link.x")) .unwrap() .write_all(include_bytes!("link.x")) .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rustc-link-search={}", out_dir.display()); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=link.x"); diff --git a/link.x b/link.x index 472e61c..aae0829 100644 --- a/link.x +++ b/link.x @@ -1,35 +1,38 @@ 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); +/* Entry point */ +ENTRY(Reset); +EXTERN(__RESET_VECTOR); /* 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); +EXTERN(__INTERRUPTS); -PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, +then the function this points to will be called before the RAM is initialized. */ +PROVIDE(PreInit = PreInit_); + +/* # Default interrupt handler */ +PROVIDE(DefaultHandler = DefaultHandler_); + +/* XXX Are there use cases for making this user overridable? */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); SECTIONS { - .vector_table ORIGIN(VECTORS) : ALIGN(2) + .vector_table 0xFFE0 : ALIGN(2) { - _sinterrupts = .; KEEP(*(.vector_table.interrupts)); - _einterrupts = .; - - KEEP(*(.vector_table.reset_vector)); - } > VECTORS + KEEP(*(.__RESET_VECTOR)); + } > ROM .text ORIGIN(ROM) : { - /* Put the reset handler first in .text section so it ends up as the entry - point of the program */ - KEEP(*(.vector_table.reset_handler)); + /* Put the reset handler and its trampoline at the beginning of the .text section */ + KEEP(*(.ResetTrampoline)); + KEEP(*(.Reset)); *(.text .text.*); } > ROM @@ -70,38 +73,25 @@ SECTIONS /* The heap starts right after the .bss + .data section ends */ _sheap = _edata; - - /* Due to an unfortunate combination of legacy concerns, - toolchain drawbacks, and insufficient attention to detail, - rustc has no choice but to mark .debug_gdb_scripts as allocatable. - We really do not want to upload it to our target, so we - remove the allocatable bit. Unfortunately, it appears - that the only way to do this in a linker script is - the extremely obscure "INFO" output section type specifier. */ - /* a rustc hack will force the program to read the first byte of this section, - so we'll set the (fake) start address of this section to something we're - sure can be read at runtime: the start of the .text section */ - .debug_gdb_scripts ORIGIN(ROM) (INFO) : { - KEEP(*(.debug_gdb_scripts)) - } } -/* Do not exceed this mark in the error messages below | */ -ASSERT(_einterrupts - _sinterrupts > 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(ORIGIN(VECTORS) + LENGTH(VECTORS) == 0x10000, " -The VECTORS memory region must end at address 0x10000. Check memory.x"); +/* Do not exceed this mark in the error messages below | */ +ASSERT(ORIGIN(ROM) + LENGTH(ROM) == 0x10000, " +ERROR(msp430-rt): The ROM memory region must end at address 0x10000. Check memory.x"); -ASSERT(_einterrupts == 0xFFFE, " -The section .vector_table.interrupts appears to be wrong. It should -end at address 0xFFFE"); +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) == 0x10000, " +ERROR(msp430-rt): .vector_table is shorter than expected. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated pac crate, if you are not +- Fix _sinterrupts in memory.x; it doesn't match the number of interrupts provided by the + pac crate +- Disable the 'device' feature of msp430-rt to build a generic application; a dependency +may be enabling it +"); 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."); +ERROR(msp430-rt): .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/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..eebf454 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "msp430-rt-macros" +version = "0.2.0" +authors = ["Jorge Aparicio "] + +[lib] +proc-macro = true + +[dependencies] +quote = "0.6.10" +proc-macro2 = "0.4.23" + +[dependencies.rand] +default-features = false +version = "0.6.0" + +[dependencies.syn] +features = ["extra-traits", "full"] +version = "0.15.20" + +[features] +device = [] \ No newline at end of file diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..2b65fca --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,452 @@ +extern crate proc_macro; +extern crate proc_macro2; +extern crate quote; +extern crate rand; +extern crate syn; + +use proc_macro::TokenStream; +use std::{ + collections::HashSet, + sync::atomic::{AtomicUsize, Ordering}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use proc_macro2::Span; +use quote::quote; +use rand::{Rng, SeedableRng}; +use syn::{ + parse, parse_macro_input, spanned::Spanned, Ident, Item, ItemFn, ItemStatic, ReturnType, Stmt, + Type, Visibility, +}; + +/// Attribute to declare the entry point of the program +/// +/// The specified function will be called by the reset handler *after* RAM has been initialized. +/// +/// The type of the specified function must be `[unsafe] fn() -> !` (never ending function) +/// +/// # Properties +/// +/// The entry point will be called by the reset handler. The program can't reference to the entry +/// point, much less invoke it. +/// +/// `static mut` variables declared within the entry point are safe to access. The compiler can't +/// prove this is safe so the attribute will help by making a transformation to the source code: for +/// this reason a variable like `static mut FOO: u32` will become `let FOO: &'static mut u32;`. Note +/// that `&'static mut` references have move semantics. +/// +/// # Examples +/// +/// - Simple entry point +/// +/// ``` no_run +/// # #![no_main] +/// # use msp430_rt_macros::entry; +/// #[entry] +/// fn main() -> ! { +/// loop { +/// /* .. */ +/// } +/// } +/// ``` +/// +/// - `static mut` variables local to the entry point are safe to modify. +/// +/// ``` no_run +/// # #![no_main] +/// # use msp430_macros::entry; +/// #[entry] +/// fn main() -> ! { +/// static mut FOO: u32 = 0; +/// +/// let foo: &'static mut u32 = FOO; +/// assert_eq!(*foo, 0); +/// *foo = 1; +/// assert_eq!(*foo, 1); +/// +/// loop { +/// /* .. */ +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as ItemFn); + + // check the function signature + let valid_signature = f.constness.is_none() + && f.vis == Visibility::Inherited + && f.abi.is_none() + && f.decl.inputs.is_empty() + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => false, + ReturnType::Type(_, ref ty) => match **ty { + Type::Never(_) => true, + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + f.span(), + "`#[entry]` function must have signature `[unsafe] fn() -> !`", + ) + .to_compile_error() + .into(); + } + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let unsafety = f.unsafety; + let hash = random_ident(); + let (statics, stmts) = match extract_static_muts(f.block.stmts) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; + + let vars = statics + .into_iter() + .map(|var| { + let attrs = var.attrs; + let ident = var.ident; + let ty = var.ty; + let expr = var.expr; + + quote!( + #[allow(non_snake_case)] + let #ident: &'static mut #ty = unsafe { + #(#attrs)* + static mut #ident: #ty = #expr; + + &mut #ident + }; + ) + }) + .collect::>(); + + quote!( + #[export_name = "main"] + #(#attrs)* + pub #unsafety fn #hash() -> ! { + #(#vars)* + + #(#stmts)* + } + ) + .into() +} + +/// Attribute to declare an interrupt handler +/// +/// When the `device` feature is disabled this attribute can only be used to override the +/// DefaultHandler. +/// +/// When the `device` feature is enabled this attribute can be used to override other interrupt +/// handlers but only when imported from a PAC (Peripheral Access Crate) crate which re-exports it. +/// Importing this attribute from the `msp430-rt` crate and using it on a function will result in a +/// compiler error. +/// +/// # Syntax +/// +/// ``` ignore +/// extern crate device; +/// +/// // the attribute comes from the device crate not from msp430-rt +/// use device::interrupt; +/// +/// #[interrupt] +/// fn USART1() { +/// // .. +/// } +/// ``` +/// +/// where the name of the function must be `DefaultHandler` or one of the device interrupts. +/// +/// # Usage +/// +/// `#[interrupt] fn Name(..` overrides the default handler for the interrupt with the given `Name`. +/// These handlers must have signature `[unsafe] fn() [-> !]`. It's possible to add state to these +/// handlers by declaring `static mut` variables at the beginning of the body of the function. These +/// variables will be safe to access from the function body. +/// +/// If the interrupt handler has not been overridden it will be dispatched by the default interrupt +/// handler (`DefaultHandler`). +/// +/// `#[interrupt] fn DefaultHandler(..` can be used to override the default interrupt handler. When +/// not overridden `DefaultHandler` defaults to an infinite loop. +/// +/// # Properties +/// +/// Interrupts handlers can only be called by the hardware. Other parts of the program can't refer +/// to the interrupt handlers, much less invoke them as if they were functions. +/// +/// `static mut` variables declared within an interrupt handler are safe to access and can be used +/// to preserve state across invocations of the handler. The compiler can't prove this is safe so +/// the attribute will help by making a transformation to the source code: for this reason a +/// variable like `static mut FOO: u32` will become `let FOO: &mut u32;`. +/// +/// # Examples +/// +/// - Using state within an interrupt handler +/// +/// ``` ignore +/// extern crate device; +/// +/// use device::interrupt; +/// +/// #[interrupt] +/// fn TIM2() { +/// static mut COUNT: i32 = 0; +/// +/// // `COUNT` is safe to access and has type `&mut i32` +/// *COUNT += 1; +/// +/// println!("{}", COUNT); +/// } +/// ``` +#[proc_macro_attribute] +pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { + let f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + let fspan = f.span(); + let ident = f.ident; + let ident_s = ident.to_string(); + + let check = if ident.to_string() == "DefaultHandler" { + None + } else if cfg!(feature = "device") { + Some(quote!(interrupt::#ident;)) + } else { + return parse::Error::new( + ident.span(), + "only the DefaultHandler can be overridden when the `device` feature is disabled", + ) + .to_compile_error() + .into(); + }; + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let block = f.block; + let stmts = block.stmts; + let unsafety = f.unsafety; + + let valid_signature = f.constness.is_none() + && f.vis == Visibility::Inherited + && f.abi.is_none() + && f.decl.inputs.is_empty() + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + fspan, + "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`", + ) + .to_compile_error() + .into(); + } + + let (statics, stmts) = match extract_static_muts(stmts) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; + + let vars = statics + .into_iter() + .map(|var| { + let attrs = var.attrs; + let ident = var.ident; + let ty = var.ty; + let expr = var.expr; + + quote!( + #[allow(non_snake_case)] + let #ident: &mut #ty = unsafe { + #(#attrs)* + static mut #ident: #ty = #expr; + + &mut #ident + }; + ) + }) + .collect::>(); + + let hash = random_ident(); + quote!( + #[export_name = #ident_s] + #(#attrs)* + #unsafety extern "msp430-interrupt" fn #hash() { + #check + + #(#vars)* + + #(#stmts)* + } + ) + .into() +} + +/// Attribute to mark which function will be called at the beginning of the reset handler. +/// +/// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph. +/// +/// The function must have the signature of `unsafe fn()`. +/// +/// The function passed will be called before static variables are initialized. Any access of static +/// variables will result in undefined behavior. +/// +/// # Examples +/// +/// ``` +/// # use msp430_macros::pre_init; +/// #[pre_init] +/// unsafe fn before_main() { +/// // do something here +/// } +/// +/// # fn main() {} +/// ``` +#[proc_macro_attribute] +pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as ItemFn); + + // check the function signature + let valid_signature = f.constness.is_none() + && f.vis == Visibility::Inherited + && f.unsafety.is_some() + && f.abi.is_none() + && f.decl.inputs.is_empty() + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + f.span(), + "`#[pre_init]` function must have signature `unsafe fn()`", + ) + .to_compile_error() + .into(); + } + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let ident = f.ident; + let block = f.block; + + quote!( + #[export_name = "__pre_init"] + #(#attrs)* + pub unsafe fn #ident() #block + ) + .into() +} + +// Creates a random identifier +fn random_ident() -> Ident { + static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); + + let secs = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + let count: u64 = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u64; + let mut seed: [u8; 16] = [0; 16]; + + for (i, v) in seed.iter_mut().take(8).enumerate() { + *v = ((secs >> (i * 8)) & 0xFF) as u8 + } + + for (i, v) in seed.iter_mut().skip(8).enumerate() { + *v = ((count >> (i * 8)) & 0xFF) as u8 + } + + let mut rng = rand::rngs::SmallRng::from_seed(seed); + Ident::new( + &(0..16) + .map(|i| { + if i == 0 || rng.gen() { + ('a' as u8 + rng.gen::() % 25) as char + } else { + ('0' as u8 + rng.gen::() % 10) as char + } + }) + .collect::(), + Span::call_site(), + ) +} + +/// Extracts `static mut` vars from the beginning of the given statements +fn extract_static_muts(stmts: Vec) -> Result<(Vec, Vec), parse::Error> { + let mut istmts = stmts.into_iter(); + + let mut seen = HashSet::new(); + let mut statics = vec![]; + let mut stmts = vec![]; + while let Some(stmt) = istmts.next() { + match stmt { + Stmt::Item(Item::Static(var)) => { + if var.mutability.is_some() { + if seen.contains(&var.ident) { + return Err(parse::Error::new( + var.ident.span(), + format!("the name `{}` is defined multiple times", var.ident), + )); + } + + seen.insert(var.ident.clone()); + statics.push(var); + } else { + stmts.push(Stmt::Item(Item::Static(var))); + } + } + _ => { + stmts.push(stmt); + break; + } + } + } + + stmts.extend(istmts); + + Ok((statics, stmts)) +} diff --git a/src/lang_items.rs b/src/lang_items.rs deleted file mode 100644 index 94546cb..0000000 --- a/src/lang_items.rs +++ /dev/null @@ -1,64 +0,0 @@ -use core::panic::PanicInfo; - -/// Default panic handler -#[cfg(feature = "abort-on-panic")] -#[panic_implementation] -fn panic(_info: &PanicInfo) -> ! { - // Disable interrupts to prevent further damage. - ::msp430::interrupt::disable(); - loop { - // Prevent optimizations that can remove this loop. - ::msp430::asm::barrier(); - } -} - -// 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. -#[cfg(has_termination_lang)] -#[lang = "start"] -extern "C" fn start(main: fn() -> T, _argc: isize, _argv: *const *const u8) -> isize -where - T: Termination, -{ - main(); - - 0 -} - -#[cfg(not(has_termination_lang))] -#[lang = "start"] -extern "C" fn start(main: fn(), _argc: isize, _argv: *const *const u8) -> isize { - main(); - - 0 -} - -#[lang = "termination"] -#[cfg(has_termination_lang)] -pub trait Termination { - fn report(self) -> i32; -} - -#[cfg(has_termination_lang)] -impl Termination for () { - fn report(self) -> i32 { - 0 - } -} diff --git a/src/lib.rs b/src/lib.rs index 0d3503d..ba18fd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,395 +1,400 @@ -//! Minimal startup / runtime for MSP430 microcontrollers +//! Startup code and minimal runtime for MSP430 microcontrollers //! //! This crate is based on [cortex-m-rt](https://docs.rs/cortex-m-rt) //! crate by Jorge Aparicio (@japaric). //! -//! # Features -//! -//! This crate provides +//! This crate contains all the required parts to build a `no_std` application (binary crate) that +//! targets a MSP430 microcontroller. //! -//! - Before main initialization of the `.bss` and `.data` sections. +//! # Features //! -//! - A `panic_fmt` implementation that just calls abort that you can opt into -//! through the "abort-on-panic" Cargo feature. If you don't use this feature -//! you'll have to provide the `panic_fmt` lang item yourself. Documentation -//! [here](https://doc.rust-lang.org/unstable-book/language-features/lang-items.html) +//! This crates takes care of: //! -//! - A minimal `start` lang item to support the standard `fn main()` -//! interface. (NOTE: The processor goes into infinite loop after -//! returning from `main`) +//! - The memory layout of the program. In particular, it populates the vector table so the device +//! can boot correctly, and properly dispatch interrupts. //! -//! - A linker script that encodes the memory layout of a generic MSP430 -//! microcontroller. This linker script is missing some information that must -//! be supplied through a `memory.x` file (see example below). +//! - Initializing `static` variables before the program entry point. //! -//! - A default exception handler that can be overridden using the -//! [`default_handler!`](macro.default_handler.html) macro. +//! This crate also provides the following attributes: //! -//! - A `_sheap` symbol at whose address you can locate a heap. +//! - `#[entry]` to declare the entry point of the program +//! - `#[pre_init]` to run code *before* `static` variables are initialized //! -//! # Example +//! This crate also implements a related attribute called `#[interrupt]`, which allows you +//! to define interrupt handlers. However, since which interrupts are available depends on the +//! microcontroller in use, this attribute should be re-exported and used from a PAC crate. //! -//! Creating a new bare metal project. (I recommend you use the -//! [`msp430-quickstart`][qs] template as it takes of all the boilerplate -//! shown here) +//! The documentation for these attributes can be found in the [Reexports](#reexports) section. //! -//! [qs]: https://github.com/japaric/msp430-quickstart/ +//! # Requirements //! -//! ``` text -//! $ cargo new --bin app && cd $_ +//! ## `memory.x` //! -//! $ # add this crate as a dependency -//! $ edit Cargo.toml && cat $_ -//! [dependencies.msp430-rt] -//! features = ["abort-on-panic"] -//! version = "0.1.0" +//! 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` //! -//! $ # tell Xargo which standard crates to build -//! $ edit Xargo.toml && cat $_ -//! [dependencies.core] -//! stage = 0 +//! ### `MEMORY` //! -//! [dependencies.compiler_builtins] -//! features = ["mem"] -//! stage = 1 +//! 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. //! -//! $ # memory layout of the device -//! $ edit memory.x && cat $_ +//! ``` text +//! /* Linker script for the MSP430G2553 */ //! MEMORY //! { -//! RAM : ORIGIN = 0x0200, LENGTH = 0x0200 -//! ROM : ORIGIN = 0xC000, LENGTH = 0x3FDE -//! VECTORS : ORIGIN = 0xFFE0, LENGTH = 0x0020 +//! RAM : ORIGIN = 0x0200, LENGTH = 0x0200 +//! ROM : ORIGIN = 0xC000, LENGTH = 0x4000 //! } -//! -//! $ edit src/main.rs && cat $_ //! ``` //! -//! ``` ignore,no_run -//! #![feature(used)] +//! # An example +//! +//! This section presents a minimal application built on top of `msp430-rt`. +//! +//! ``` ignore +//! // IMPORTANT the standard `main` interface is not used because it requires nightly +//! #![no_main] //! #![no_std] //! +//! extern crate msp430; //! extern crate msp430_rt; //! -//! fn main() { -//! // do something here -//! } +//! use msp430::asm; +//! use msp430_rt::entry; //! -//! // As we are not using interrupts, we just register a dummy catch all -//! // handler -//! #[link_section = ".vector_table.interrupts"] -//! #[used] -//! static INTERRUPTS: [extern "msp430-interrupt" fn(); 15] = -//! [default_handler; 15]; +//! // use `main` as the entry point of this application +//! // `main` is not allowed to return +//! #[entry] +//! fn main() -> ! { +//! // initialization +//! +//! loop { +//! // application logic +//! } +//! } //! -//! extern "msp430-interrupt" fn default_handler() { +//! // Panicking behavior +//! #[panic_handler] +//! fn panic(_: &PanicInfo) -> ! { //! loop { +//! asm::barrier(); //! } //! } //! ``` //! +//! 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 `msp430-rt`'s +//! linker script: `link.x`. The required steps are shown below: +//! //! ``` text -//! $ cargo install xargo +//! $ cat > memory.x <: -//! c000: 31 40 00 04 mov #1024, r1 ;#0x0400 -//! c004: 30 40 28 c0 br #0xc028 ; -//! ``` +//! ## `device` //! -//! # Symbol interfaces +//! 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 +//! interrupt handler. This makes the final application device agnostic: you will be able to run it +//! on any MSP430 device -- provided that you correctly specified its memory layout in `memory.x` +//! -- without hitting undefined behavior. //! -//! 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. +//! 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 PAC crates generated using `svd2rust`. Those *PAC crates* will populate the +//! missing part of the vector table when their `"rt"` feature is enabled. //! -//! ## `DEFAULT_HANDLER` +//! # Inspection //! -//! 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: +//! This section covers how to inspect a binary that builds on top of `msp430-rt`. //! -//! ``` ignore,no_run -//! #[no_mangle] -//! pub extern "msp430-interrupt" fn DEFAULT_HANDLER() { -//! // do something here -//! } +//! ## Sections (`size`) +//! +//! `msp430-rt` uses standard sections like `.text`, `.rodata`, `.bss` and `.data` as one would +//! expect. `msp430-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`). +//! +//! ``` text +//! $ size -Ax target/thumbv7m-none-eabi/examples/app +//! section size addr +//! .vector_table 0x20 0xffe0 +//! .text 0x44 0xc000 +//! .rodata 0x0 0xc044 +//! .bss 0x0 0x200 +//! .data 0x0 0x200 +//! .MSP430.attributes 0x17 0x0 +//! Total 0x7b //! ``` //! -//! ## `.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. +//! ``` text +//! $ size target/msp430-none-elf/examples/app +//! text data bss dec hex filename +//! 100 0 0 100 64 target/msp430-none-elf/release/app +//! ``` //! -//! ### Example +//! ## Symbols (`objdump`, `nm`) //! -//! Populating the vector table +//! One will always find the following (unmangled) symbols in `msp430-rt` applications: //! -//! ``` ignore,no_run -//! // Number of interrupts the device has -//! const N: usize = 15; +//! - `ResetTrampoline`. This is the reset handler. The microcontroller will executed this function +//! upon booting. This trampoline simply initializes the stack pointer and the jumps to `Reset`. //! -//! // Default interrupt handler that just calls the `DEFAULT_HANDLER` -//! #[linkage = "weak"] -//! #[naked] -//! #[no_mangle] -//! extern "msp430-interrupt" fn WWDG() { -//! unsafe { -//! asm!("b DEFAULT_HANDLER" :::: "volatile"); -//! core::intrinsics::unreachable(); -//! } -//! } +//! - `Reset`. This function will call the user program entry point (See `#[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` and you won't find it. //! -//! // You need one function per interrupt handler -//! #[linkage = "weak"] -//! #[naked] -//! #[no_mangle] -//! extern "msp430-interrupt" fn WWDG() { -//! unsafe { -//! asm!("b DEFAULT_HANDLER" :::: "volatile"); -//! core::intrinsics::unreachable(); -//! } -//! } +//! - `DefaultHandler`. This is the default interrupt handler. If not overridden using `#[interrupt] +//! fn DefaultHandler(..` this will be an infinite loop. //! -//! // .. +//! - `__RESET_VECTOR`. This is the reset vector, a pointer into `ResetTrampoline`. This vector is +//! located at the end of the `.vector_table` section. //! -//! // 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), -//! // .. -//! ]; -//! ``` +//! - `__INTERRUPTS`. This is the device specific interrupt portion of the vector table. This array +//! is located right before `__RESET_VECTOR` in the `.vector_table` section. //! -//! Overriding an interrupt (this can be in a different crate) +//! - `PreInit`. This is a function to be run before RAM is initialized. It defaults to an empty +//! function. The function called can be changed using the `#[pre_init]` attribute. The empty +//! function is not optimized out by default, but if an empty function is marked with the +//! `#[pre_init]` attribute then the function call will be optimized out. //! -//! ``` 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 "msp430-interrupt" fn WWDG() { -//! // do something here +//! If you overrode any interrupt handler you'll find it as an unmangled symbol, e.g. `NMI` or +//! `WDT`, in the output of `objdump`, +//! +//! # Advanced usage +//! +//! ## Setting the program entry point +//! +//! 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. +//! +//! The `Reset` handler will call a symbol named `main` (unmangled) *after* initializing `.bss` and +//! `.data`. `#[entry]` provides this symbol in its expansion: +//! +//! ``` ignore +//! #[entry] +//! fn main() -> ! { +//! /* user code */ +//! } +//! +//! // expands into +//! +//! #[export_name = "main"] +//! extern "C" fn randomly_generated_string() -> ! { +//! /* user code */ //! } //! ``` //! -//! ## `memory.x` +//! The unmangled `main` symbol must have signature `extern "C" fn() -> !` or its invocation from +//! `Reset` will result in undefined behavior. //! -//! This file supplies the information about the device to the linker. +//! ## Incorporating device specific interrupts //! -//! ### `MEMORY` +//! 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 `msp430-rt` read on. //! -//! 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. +//! The information in this section applies when the `"device"` feature has been enabled. //! -//! The program instructions (the `.text` section) will be stored in the memory -//! region named ROM, and the program `static` variables (the sections `.bss` -//! and `.data`) will be allocated in the memory region named RAM. +//! ### `__INTERRUPTS` //! -//! ### `_stack_start` +//! 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. //! -//! 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 `static` variable will be placed at `ORIGIN(FLASH) + 0x40`. This address corresponds to the +//! spot where IRQ0 (IRQ number 0) is located. //! -//! If omitted this symbol value will default to `ORIGIN(RAM) + LENGTH(RAM)`. +//! To conform to the MSP430 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. //! -//! Allocating the call stack on a different RAM region. +//! ``` ignore +//! union Vector { +//! handler: extern "msp430-abi" fn(), +//! reserved: usize, +//! } //! -//! ```,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 +//! extern "msp430-abi" { +//! fn Foo(); +//! fn Bar(); //! } //! -//! _stack_start = ORIGIN(CCRAM) + LENGTH(CCRAM); +//! #[link_section = ".vector_table.interrupts"] +//! #[no_mangle] +//! static __INTERRUPTS: [Vector; 15] = [ +//! // 0-1: Reserved +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! +//! // 2: Foo +//! Vector { handler: Foo }, +//! +//! // 3: Reserved +//! Vector { reserved: 0 }, +//! +//! // 4: Bar +//! Vector { handler: Bar }, +//! +//! // 5-14: Reserved +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! Vector { reserved: 0 }, +//! ]; //! ``` //! -//! ### `_stext` +//! ### `device.x` //! -//! 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. +//! 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. //! -//! 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. +//! 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. //! -//! #### Example +//! 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 `msp430-rt` +//! uses; when the `"device"` feature is enabled `msp430-rt`'s linker script (`link.x`) depends on +//! a linker script named `device.x`. The crate that provides `__INTERRUPTS` must also provide this +//! file. //! -//! Locate the `.text` section 1024 bytes after the start of the FLASH region. +//! For our running example the `device.x` linker script looks like this: //! -//! ```,ignore -//! _stext = ORIGIN(FLASH) + 0x400; +//! ``` text +//! /* device.x */ +//! PROVIDE(Foo = DefaultHandler); +//! PROVIDE(Bar = DefaultHandler); //! ``` //! -//! ### `_sheap` -//! -//! 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. +//! This weakly aliases both `Foo` and `Bar`. `DefaultHandler` is the default interrupt handler. //! -//! #### Example +//! 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 -//! extern crate some_allocator; -//! -//! // Size of the heap in bytes -//! const SIZE: usize = 1024; -//! -//! extern "C" { -//! static mut _sheap: u8; -//! } +//! ``` ignore +//! use std::{env, fs::File, io::Write, 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()); //! } //! ``` -#![cfg_attr(target_arch = "msp430", feature(core_intrinsics))] -#![cfg_attr(feature = "abort-on-panic", feature(panic_implementation))] #![deny(missing_docs)] #![feature(abi_msp430_interrupt)] -#![feature(asm)] -#![feature(lang_items)] -#![feature(linkage)] -#![feature(naked_functions)] -#![feature(used)] #![no_std] -extern crate msp430; -#[cfg(target_arch = "msp430")] -extern crate r0; +use msp430::asm; +pub use msp430_rt_macros::interrupt; +pub use msp430_rt_macros::{entry, pre_init}; -#[cfg(not(test))] -mod lang_items; +/// Returns a pointer to the start of the heap +/// +/// The returned pointer is guaranteed to be 4-byte aligned. +#[inline] +pub fn heap_start() -> *mut u32 { + extern "C" { + static mut __sheap: u32; + } -#[cfg(target_arch = "msp430")] -extern "C" { - // NOTE `rustc` forces this signature on us. See `src/lang_items.rs` - fn main(argc: isize, argv: *const *const u8) -> isize; + unsafe { &mut __sheap } +} - // Boundaries of the .bss section - static mut _ebss: u16; - static mut _sbss: u16; +extern "msp430-interrupt" { + fn ResetTrampoline() -> !; +} - // Boundaries of the .data section - static mut _edata: u16; - static mut _sdata: u16; +#[link_section = ".__RESET_VECTOR"] +#[no_mangle] +static __RESET_VECTOR: unsafe extern "msp430-interrupt" fn() -> ! = ResetTrampoline; - // Initial values of the .data section (stored in ROM) - static _sidata: u16; -} +// The reset handler +#[no_mangle] +#[link_section = ".Reset"] +unsafe extern "C" fn Reset() -> ! { + extern "C" { + // Boundaries of the .bss section + static mut _ebss: u16; + static mut _sbss: u16; -/// The reset handler -/// -/// This is the entry point of all programs -#[cfg(target_arch = "msp430")] -unsafe extern "C" fn reset_handler() -> ! { - r0::zero_bss(&mut _sbss, &mut _ebss); - r0::init_data(&mut _sdata, &mut _edata, &_sidata); + // Boundaries of the .data section + static mut _edata: u16; + static mut _sdata: u16; - // Neither `argc` or `argv` make sense in bare metal context so we - // just stub them - main(0, ::core::ptr::null()); + // Initial values of the .data section (stored in ROM) + static _sidata: u16; + } - // If `main` returns, then we go into "reactive" mode and simply attend - // interrupts as they occur. - loop { - // Prevent optimizations that can remove this loop. - ::msp430::asm::barrier(); + extern "Rust" { + fn PreInit(); + fn main() -> !; } - // This is the real entry point - #[link_section = ".vector_table.reset_handler"] - #[naked] - unsafe extern "msp430-interrupt" fn trampoline() -> ! { - // "trampoline" to get to the real reset handler. - asm!("mov #_stack_start, r1 - br $0" - : - : "i"(reset_handler as unsafe extern "C" fn() -> !) - : - : "volatile" - ); + PreInit(); - ::core::intrinsics::unreachable() - } + r0::zero_bss(&mut _sbss, &mut _ebss); + r0::init_data(&mut _sdata, &mut _edata, &_sidata); - #[link_section = ".vector_table.reset_vector"] - #[used] - static RESET_VECTOR: unsafe extern "msp430-interrupt" fn() -> ! = - trampoline; + main() } -#[export_name = "DEFAULT_HANDLER"] -#[linkage = "weak"] -extern "msp430-interrupt" fn default_handler() { +#[no_mangle] +unsafe extern "C" fn PreInit_() {} + +#[no_mangle] +extern "msp430-interrupt" fn DefaultHandler_() { // The interrupts are already disabled here. loop { // Prevent optimizations that can remove this loop. - ::msp430::asm::barrier(); + asm::barrier(); } } -// make sure the compiler emits the DEFAULT_HANDLER symbol so the linker can -// find it! -#[used] -static KEEP: extern "msp430-interrupt" fn() = default_handler; - -/// This macro lets you override the default 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()` -/// -/// # Examples -/// -/// ``` ignore -/// default_handler!(foo::bar); -/// -/// mod foo { -/// pub fn bar() { -/// loop {} -/// } -/// } -/// ``` -#[macro_export] -macro_rules! default_handler { - ($path:path) => { - #[allow(non_snake_case)] - #[doc(hidden)] - #[no_mangle] - pub unsafe extern "msp430-interrupt" fn DEFAULT_HANDLER() { - // type checking - let f: fn() = $path; - f(); - } - } +extern "msp430-interrupt" { + fn DefaultHandler(); } + +// Interrupts for generic application +#[cfg(not(feature = "device"))] +#[no_mangle] +#[link_section = ".vector_table.interrupts"] +static __INTERRUPTS: [unsafe extern "msp430-interrupt" fn(); 15] = [DefaultHandler; 15]; From 0b643eed81f82762ad74bad56739d79ab3e2ebbc Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Thu, 19 Dec 2019 22:39:48 -0500 Subject: [PATCH 2/6] Use explicit VECTORS memory region for now- different device families have different vector start locations. --- link.x | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/link.x b/link.x index aae0829..69d7131 100644 --- a/link.x +++ b/link.x @@ -22,11 +22,11 @@ _stack_start = ORIGIN(RAM) + LENGTH(RAM); SECTIONS { - .vector_table 0xFFE0 : ALIGN(2) + .vector_table ORIGIN(VECTORS) : ALIGN(2) { KEEP(*(.vector_table.interrupts)); KEEP(*(.__RESET_VECTOR)); - } > ROM + } > VECTORS .text ORIGIN(ROM) : { @@ -76,8 +76,8 @@ SECTIONS } /* Do not exceed this mark in the error messages below | */ -ASSERT(ORIGIN(ROM) + LENGTH(ROM) == 0x10000, " -ERROR(msp430-rt): The ROM memory region must end at address 0x10000. Check memory.x"); +ASSERT(ORIGIN(VECTORS) + LENGTH(VECTORS) == 0x10000, " +ERROR(msp430-rt): The VECTORS memory region must end at address 0x10000. Check memory.x"); ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) == 0x10000, " ERROR(msp430-rt): .vector_table is shorter than expected. From 2c7aabaa316f37455d3c4e0f575a5c28a36ca8dc Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Fri, 20 Dec 2019 01:22:54 -0500 Subject: [PATCH 3/6] If using the device feature, make sure peripheral interrupt handler symbols are PROVIDEd. --- build.rs | 26 ++++++++++++++++++++++---- link.x => link.x.in | 0 2 files changed, 22 insertions(+), 4 deletions(-) rename link.x => link.x.in (100%) diff --git a/build.rs b/build.rs index dcb40ff..994cc57 100644 --- a/build.rs +++ b/build.rs @@ -10,10 +10,28 @@ fn main() { } // Put the linker script somewhere the linker can find it - File::create(out_dir.join("link.x")) - .unwrap() - .write_all(include_bytes!("link.x")) - .unwrap(); + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let link_x = include_bytes!("link.x.in"); + if env::var_os("CARGO_FEATURE_DEVICE").is_some() { + let mut f = File::create(out.join("link.x")).unwrap(); + + f.write_all(link_x).unwrap(); + + // *IMPORTANT*: The weak aliases (i.e. `PROVIDED`) must come *after* `EXTERN(__INTERRUPTS)`. + // Otherwise the linker will ignore user defined interrupts and always populate the table + // with the weak aliases. + 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(); + } else { + let mut f = File::create(out.join("link.x")).unwrap(); + f.write_all(link_x).unwrap(); + }; + println!("cargo:rustc-link-search={}", out_dir.display()); println!("cargo:rerun-if-changed=build.rs"); diff --git a/link.x b/link.x.in similarity index 100% rename from link.x rename to link.x.in From 06a14bdbd5f9f31eacf6d0812f89b67d53d3e7e5 Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Fri, 20 Dec 2019 23:22:54 -0500 Subject: [PATCH 4/6] Change Reset trampoline from symbolic address mode to immediate due to potential bug. --- asm.s | 3 ++- bin/msp430-none-elf.a | Bin 928 -> 924 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/asm.s b/asm.s index 5e2c79f..84d8632 100644 --- a/asm.s +++ b/asm.s @@ -3,4 +3,5 @@ .type ResetTrampoline,%function ResetTrampoline: mov #_stack_start,r1 - br Reset + br #Reset ; XXX "br Reset" should also work, but doesn't on G2553, + ; and I don't know why. diff --git a/bin/msp430-none-elf.a b/bin/msp430-none-elf.a index 42973405e5e24035d27248151931ed970a21abe7..1247804f4ffbedab99552fb4838da4e606d564d1 100644 GIT binary patch delta 177 zcmZ3$K8JmR1ec+yxtXPbp{0TOL?tg}1W!T1Kmi0Gq@jUZNm^mg3GO15uWS?xow1G1Qs6_&( um}BxsCiBS^%mS>zKwju%QD$Y%6+qSmAm*Ix$Sf~m0aU31)Oa@~lsQES1TE%*w0^K$W4B3z>ugZBZP- From 7e9d7e8b1deb6cc70b3655f998be94da51ff35bf Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Sat, 21 Dec 2019 01:06:11 -0500 Subject: [PATCH 5/6] Improve DefaultHandler handling (remove warning, ensure __INTERRUPTS isn't zeroed out). --- link.x.in | 3 +++ src/lib.rs | 14 ++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/link.x.in b/link.x.in index 69d7131..3afae8f 100644 --- a/link.x.in +++ b/link.x.in @@ -15,6 +15,9 @@ then the function this points to will be called before the RAM is initialized. * PROVIDE(PreInit = PreInit_); /* # Default interrupt handler */ +EXTERN(DefaultHandler); /* If this line is not here, all unused interrupt + handlers will be zeroed out instead of doing + to the DefaultHandler! */ PROVIDE(DefaultHandler = DefaultHandler_); /* XXX Are there use cases for making this user overridable? */ diff --git a/src/lib.rs b/src/lib.rs index ba18fd4..9dae4b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -381,7 +381,7 @@ unsafe extern "C" fn Reset() -> ! { unsafe extern "C" fn PreInit_() {} #[no_mangle] -extern "msp430-interrupt" fn DefaultHandler_() { +extern "msp430-interrupt" fn DefaultHandler_() -> ! { // The interrupts are already disabled here. loop { // Prevent optimizations that can remove this loop. @@ -389,12 +389,14 @@ extern "msp430-interrupt" fn DefaultHandler_() { } } -extern "msp430-interrupt" { - fn DefaultHandler(); -} - // Interrupts for generic application #[cfg(not(feature = "device"))] #[no_mangle] #[link_section = ".vector_table.interrupts"] -static __INTERRUPTS: [unsafe extern "msp430-interrupt" fn(); 15] = [DefaultHandler; 15]; +static __INTERRUPTS: [unsafe extern "msp430-interrupt" fn(); 15] = [{ + extern "msp430-interrupt" { + fn DefaultHandler(); + } + + DefaultHandler +}; 15]; From 99a4d401d85576473f65925bb15c480e4535d9d7 Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Sun, 22 Dec 2019 00:20:54 -0500 Subject: [PATCH 6/6] Update documentation to reflect added changes. --- src/lib.rs | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9dae4b8..3e152f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,8 @@ //! to define interrupt handlers. However, since which interrupts are available depends on the //! microcontroller in use, this attribute should be re-exported and used from a PAC crate. //! -//! The documentation for these attributes can be found in the [Reexports](#reexports) section. +//! The documentation for these attributes can be found in the [Attribute Macros](#attributes) +//! section. //! //! # Requirements //! @@ -35,17 +36,30 @@ //! //! ### `MEMORY` //! -//! 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. +//! The linker script must specify the memory available in the device as, at least, three `MEMORY` +//! regions: one named `ROM`, one named `RAM`, and one named `VECTORS`. The `.text` and `.rodata` +//! sections of the program will be placed in the `ROM` region, whereas the `.bss` and `.data` +//! sections, as well as the heap, will be placed in the `RAM` region. The `.vector_table` section, +//! which including the interrupt vectors and reset address, will be placed in the `VECTORS` +//! region at the end of flash. The `ROM` region should end at the address the `VECTORS` region +//! begins. +//! +//! A `VECTORS` region is required because between (_and within_) msp430 device families: +//! * Devices do not have a constant single vector table size. +//! * Devices do not have a constant vector table start address. +//! Consult your Family User's Guide (e.g. MSP430x5xx Family User's Guide, slau208), +//! particularly the Memory Map section, and your device's datasheet (e.g. msp430g2553) for +//! information on vector table layout and size. _You may be able to get more program space if +//! your device's datasheet explicitly marks a contiguous set of vectors as unused!_ +//! //! //! ``` text //! /* Linker script for the MSP430G2553 */ //! MEMORY //! { //! RAM : ORIGIN = 0x0200, LENGTH = 0x0200 -//! ROM : ORIGIN = 0xC000, LENGTH = 0x4000 +//! ROM : ORIGIN = 0xC000, LENGTH = 0x3FE0 +//! VECTORS : ORIGIN = 0xFFE0, LENGTH = 0x20 //! } //! ``` //! @@ -58,10 +72,10 @@ //! #![no_main] //! #![no_std] //! -//! extern crate msp430; //! extern crate msp430_rt; +//! // Simple panic handler that infinitely loops. +//! extern crate panic_msp430; //! -//! use msp430::asm; //! use msp430_rt::entry; //! //! // use `main` as the entry point of this application @@ -75,13 +89,6 @@ //! } //! } //! -//! // Panicking behavior -//! #[panic_handler] -//! fn panic(_: &PanicInfo) -> ! { -//! loop { -//! asm::barrier(); -//! } -//! } //! ``` //! //! To actually build this program you need to place a `memory.x` linker script somewhere the linker @@ -94,7 +101,8 @@ //! MEMORY //! { //! RAM : ORIGIN = 0x0200, LENGTH = 0x0200 -//! ROM : ORIGIN = 0xC000, LENGTH = 0x4000 +//! ROM : ORIGIN = 0xC000, LENGTH = 0x3FE0 +//! VECTORS : ORIGIN = 0xFFE0, LENGTH = 0x20 //! } //! EOF //! @@ -132,7 +140,7 @@ //! used by actual instructions (`.text`) and constants (`.rodata`). //! //! ``` text -//! $ size -Ax target/thumbv7m-none-eabi/examples/app +//! $ size -Ax target/msp430-none-elf/examples/app //! section size addr //! .vector_table 0x20 0xffe0 //! .text 0x44 0xc000 @@ -221,7 +229,7 @@ //! variable named`__INTERRUPTS` (unmangled) that must be placed in the `.vector_table.interrupts` //! section of its object file. //! -//! This `static` variable will be placed at `ORIGIN(FLASH) + 0x40`. This address corresponds to the +//! This `static` variable will be placed at `ORIGIN(VECTORS)`. This address corresponds to the //! spot where IRQ0 (IRQ number 0) is located. //! //! To conform to the MSP430 ABI `__INTERRUPTS` must be an array of function pointers; some spots @@ -235,11 +243,11 @@ //! //! ``` ignore //! union Vector { -//! handler: extern "msp430-abi" fn(), +//! handler: extern "msp430-interrupt" fn(), //! reserved: usize, //! } //! -//! extern "msp430-abi" { +//! extern "msp430-interrupt" { //! fn Foo(); //! fn Bar(); //! } @@ -318,6 +326,10 @@ //! println!("cargo:rustc-link-search={}", out.display()); //! } //! ``` +//! +//! [attr-entry]: attr.entry.html +//! [attr-exception]: attr.exception.html +//! [attr-pre_init]: attr.pre_init.html #![deny(missing_docs)] #![feature(abi_msp430_interrupt)]