Просмотр исходного кода

Merge #31

31: New linker script and multi-core support r=laanwj a=Disasm

* Linker script was reworked. Now it uses region aliases to relocate sections. This approach makes it possible to build firmware for both FLASH+RAM and RAM-only targets. Memory definitions now supposed to be present in their corresponding crates (e.g. RAM definition in PAC crate, FLASH definition in board support crate).
* Multi-core support was introduced. Cores are parked with `_mp_hook` function and then awakened in platform-dependent way.
* Documentation was updated to reflect new features.
* New crate version: 0.6.0

Depends on: https://github.com/rust-embedded/riscv/pull/28
Closes: https://github.com/rust-embedded/riscv-rt/issues/26

Co-authored-by: Vadim Kaushan <admin@disasm.info>
bors[bot] 5 лет назад
Родитель
Сommit
b66f595ea5

+ 7 - 3
riscv-rt/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "riscv-rt"
-version = "0.5.0"
+version = "0.6.0"
 repository = "https://github.com/rust-embedded/riscv-rt"
 authors = ["The RISC-V Team <risc-v@teams.rust-embedded.org>"]
 categories = ["embedded", "no-std"]
@@ -10,8 +10,12 @@ license = "ISC"
 
 [dependencies]
 r0 = "0.2.2"
-riscv = "0.5.0"
-riscv-rt-macros = { path = "macros", version = "0.1.5" }
+riscv = "0.5.3"
+riscv-rt-macros = { path = "macros", version = "0.1.6" }
 
 [features]
 inline-asm = ["riscv/inline-asm"]
+
+[dev-dependencies]
+riscv = "0.5.3"
+panic-halt = "0.2.0"

+ 52 - 1
riscv-rt/asm.S

@@ -13,15 +13,66 @@ _start:
     .cfi_startproc
     .cfi_undefined ra
 
+    csrw mie, 0
+    csrw mip, 0
+
+    li  x1, 0
+    li  x2, 0
+    li  x3, 0
+    li  x4, 0
+    li  x5, 0
+    li  x6, 0
+    li  x7, 0
+    li  x8, 0
+    li  x9, 0
+    li  x10,0
+    li  x11,0
+    li  x12,0
+    li  x13,0
+    li  x14,0
+    li  x15,0
+    li  x16,0
+    li  x17,0
+    li  x18,0
+    li  x19,0
+    li  x20,0
+    li  x21,0
+    li  x22,0
+    li  x23,0
+    li  x24,0
+    li  x25,0
+    li  x26,0
+    li  x27,0
+    li  x28,0
+    li  x29,0
+    li  x30,0
+    li  x31,0
+
     .option push
     .option norelax
     la gp, __global_pointer$
     .option pop
 
+    // Check hart id
+    csrr a2, mhartid
+    lui t0, %hi(_max_hart_id)
+    add t0, t0, %lo(_max_hart_id)
+    bgtu a2, t0, abort
+
+    // Allocate stacks
     la sp, _stack_start
+    lui t0, %hi(_hart_stack_size)
+    add t0, t0, %lo(_hart_stack_size)
+    mul t0, a2, t0
+    sub sp, sp, t0
 
+    // Set frame pointer
     add s0, sp, zero
 
+    // Set trap handler
+    la t0, _start_trap
+    csrw mtvec, t0
+
     jal zero, _start_rust
 
     .cfi_endproc
@@ -83,4 +134,4 @@ _start_trap:
 .section .init
 .globl abort
 abort:
-    jal zero, _start
+    j abort

BIN
riscv-rt/bin/riscv32imac-unknown-none-elf.a


BIN
riscv-rt/bin/riscv32imc-unknown-none-elf.a


BIN
riscv-rt/bin/riscv64gc-unknown-none-elf.a


BIN
riscv-rt/bin/riscv64imac-unknown-none-elf.a


+ 3 - 0
riscv-rt/ci/script.sh

@@ -2,6 +2,9 @@ set -euxo pipefail
 
 main() {
     cargo check --target $TARGET
+    if [[ $TARGET == riscv* ]]; then
+        cargo check --target $TARGET --examples
+    fi
 
     if [ $TRAVIS_RUST_VERSION = nightly ]; then
         cargo check --target $TARGET --features 'inline-asm'

+ 13 - 0
riscv-rt/examples/empty.rs

@@ -0,0 +1,13 @@
+#![no_std]
+#![no_main]
+
+extern crate panic_halt;
+extern crate riscv_rt;
+
+use riscv_rt::entry;
+
+#[entry]
+fn main() -> ! {
+    // do something here
+    loop { }
+}

+ 56 - 0
riscv-rt/examples/multi_core.rs

@@ -0,0 +1,56 @@
+#![no_std]
+#![no_main]
+
+extern crate panic_halt;
+extern crate riscv;
+extern crate riscv_rt;
+
+use riscv::register::{mie, mip, mhartid};
+use riscv::asm::wfi;
+use riscv_rt::entry;
+
+#[export_name = "_mp_hook"]
+pub extern "Rust" fn user_mp_hook() -> bool {
+    let hartid = mhartid::read();
+    if hartid == 0 {
+        true
+    } else {
+        let addr = 0x02000000 + hartid * 4;
+        unsafe {
+            // Clear IPI
+            (addr as *mut u32).write_volatile(0);
+
+            // Start listening for software interrupts
+            mie::set_msoft();
+
+            loop {
+                wfi();
+                if mip::read().msoft() {
+                    break;
+                }
+            }
+
+            // Start listening for software interrupts
+            mie::clear_msoft();
+
+            // Clear IPI
+            (addr as *mut u32).write_volatile(0);
+        }
+        false
+    }
+}
+
+#[entry]
+fn main() -> ! {
+    let hartid = mhartid::read();
+
+    if hartid == 0 {
+        // Waking hart 1...
+        let addr = 0x02000004;
+        unsafe {
+            (addr as *mut u32).write_volatile(1);
+        }
+    }
+
+    loop { }
+}

+ 82 - 21
riscv-rt/link.x

@@ -1,7 +1,8 @@
-/* NOTE: Adapted from cortex-m/link.x */
-INCLUDE memory.x
-
-PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM));
+PROVIDE(_stext = ORIGIN(REGION_TEXT));
+PROVIDE(_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK));
+PROVIDE(_max_hart_id = 0);
+PROVIDE(_hart_stack_size = 2K);
+PROVIDE(_heap_size = 0);
 
 PROVIDE(trap_handler = default_trap_handler);
 
@@ -10,9 +11,28 @@ PROVIDE(trap_handler = default_trap_handler);
    then the function this points to will be called before the RAM is initialized. */
 PROVIDE(__pre_init = default_pre_init);
 
+/* # Multi-processing hook function
+   fn _mp_hook() -> bool;
+
+   This function is called from all the harts and must return true only for one hart,
+   which will perform memory initialization. For other harts it must return false
+   and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt).
+*/
+PROVIDE(_mp_hook = default_mp_hook);
+
+PHDRS
+{
+    load PT_LOAD;
+    ram_load PT_LOAD;
+    virtual PT_NULL;
+}
+
 SECTIONS
 {
-  PROVIDE(_stext = ORIGIN(FLASH));
+  .text.dummy ORIGIN(REGION_TEXT) :
+  {
+    /* This section is intended to make _stext address work */
+  } > REGION_TEXT :virtual
 
   .text ALIGN(_stext,4) :
   {
@@ -25,22 +45,14 @@ SECTIONS
     KEEP(*(.trap.rust));
 
     *(.text .text.*);
-  } > FLASH
+  } > REGION_TEXT :load
 
   .rodata ALIGN(4) :
   {
     *(.rodata .rodata.*);
-  } > FLASH
+  } > REGION_RODATA :load
 
-  .bss :
-  {
-    _sbss = .;
-    *(.bss .bss.*);
-    . = ALIGN(4);
-    _ebss = .;
-  } > RAM
-
-  .data : AT(LOADADDR(.rodata) + SIZEOF(.rodata))
+  .data ALIGN(4) :
   {
     _sidata = LOADADDR(.data);
     _sdata = .;
@@ -49,9 +61,15 @@ SECTIONS
     *(.data .data.*);
     . = ALIGN(4);
     _edata = .;
-  } > RAM
+  } > REGION_DATA AT > REGION_RODATA :ram_load
 
-  PROVIDE(_heap_size = 0);
+  .bss :
+  {
+    _sbss = .;
+    *(.bss .bss.*);
+    . = ALIGN(4);
+    _ebss = .;
+  } > REGION_BSS :virtual
 
   /* fictitious region that represents the memory available for the heap */
   .heap (INFO) :
@@ -60,7 +78,7 @@ SECTIONS
     . += _heap_size;
     . = ALIGN(4);
     _eheap = .;
-  } > RAM
+  } > REGION_HEAP :virtual
 
   /* fictitious region that represents the memory available for the stack */
   .stack (INFO) :
@@ -68,7 +86,7 @@ SECTIONS
     _estack = .;
     . = _stack_start;
     _sstack = .;
-  } > RAM
+  } > REGION_STACK :virtual
 
   /* fake output .got section */
   /* Dynamic relocations are unsupported. This section is only used to detect
@@ -86,10 +104,53 @@ SECTIONS
   }
 }
 
-/* Do not exceed this mark in the error messages below                | */
+/* Do not exceed this mark in the error messages above                                    | */
+ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_RODATA) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_DATA) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_DATA must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_STACK) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned");
+
+ASSERT(_stext % 4 == 0, "
+ERROR(riscv-rt): `_stext` must be 4-byte aligned");
+
+ASSERT(_sdata % 4 == 0 && _edata % 4 == 0, "
+BUG(riscv-rt): .data is not 4-byte aligned");
+
+ASSERT(_sidata % 4 == 0, "
+BUG(riscv-rt): the LMA of .data is not 4-byte aligned");
+
+ASSERT(_sbss % 4 == 0 && _ebss % 4 == 0, "
+BUG(riscv-rt): .bss is not 4-byte aligned");
+
+ASSERT(_sheap % 4 == 0, "
+BUG(riscv-rt): start of .heap is not 4-byte aligned");
+
+ASSERT(_stext + SIZEOF(.text) < ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT), "
+ERROR(riscv-rt): The .text section must be placed inside the REGION_TEXT region.
+Set _stext to an address smaller than 'ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT)'");
+
+ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, "
+ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts.
+Consider changing `_max_hart_id` or `_hart_stack_size`.");
+
 ASSERT(SIZEOF(.got) == 0, "
 .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.");
+
+/* Do not exceed this mark in the error messages above                                    | */

+ 1 - 4
riscv-rt/macros/Cargo.toml

@@ -10,7 +10,7 @@ keywords = ["riscv", "runtime", "startup"]
 license = "MIT OR Apache-2.0"
 name = "riscv-rt-macros"
 repository = "https://github.com/rust-embedded/riscv-rt"
-version = "0.1.5"
+version = "0.1.6"
 
 [lib]
 proc-macro = true
@@ -26,6 +26,3 @@ version = "0.15.13"
 [dependencies.rand]
 version = "0.5.5"
 default-features = false
-
-[dev-dependencies]
-riscv-rt = { path = "..", version = "0.5.0" }

+ 105 - 38
riscv-rt/src/lib.rs

@@ -13,14 +13,14 @@
 //!
 //! - Before main initialization of the `.bss` and `.data` sections.
 //!
-//! - Before main initialization of the FPU (for targets that have a FPU).
-//!
 //! - `#[entry]` to declare the entry point of the program
 //! - `#[pre_init]` to run code *before* `static` variables are initialized
 //!
 //! - A linker script that encodes the memory layout of a generic RISC-V
 //!   microcontroller. This linker script is missing some information that must
-//!   be supplied through a `memory.x` file (see example below).
+//!   be supplied through a `memory.x` file (see example below). This file
+//!   must be supplied using rustflags and listed *before* `link.x`. Arbitrary
+//!   filename can be use instead of `memory.x`.
 //!
 //! - A `_sheap` symbol at whose address you can locate a heap.
 //!
@@ -30,18 +30,24 @@
 //! $ # add this crate as a dependency
 //! $ edit Cargo.toml && cat $_
 //! [dependencies]
-//! riscv-rt = "0.4.0"
+//! riscv-rt = "0.6.0"
 //! panic-halt = "0.2.0"
 //!
 //! $ # memory layout of the device
 //! $ edit memory.x && cat $_
 //! MEMORY
 //! {
-//!   /* NOTE K = KiBi = 1024 bytes */
-//!   FLASH : ORIGIN = 0x20000000, LENGTH = 16M
 //!   RAM : ORIGIN = 0x80000000, LENGTH = 16K
+//!   FLASH : ORIGIN = 0x20000000, LENGTH = 16M
 //! }
 //!
+//! REGION_ALIAS("REGION_TEXT", FLASH);
+//! REGION_ALIAS("REGION_RODATA", FLASH);
+//! REGION_ALIAS("REGION_DATA", RAM);
+//! REGION_ALIAS("REGION_BSS", RAM);
+//! REGION_ALIAS("REGION_HEAP", RAM);
+//! REGION_ALIAS("REGION_STACK", RAM);
+//!
 //! $ edit src/main.rs && cat $_
 //! ```
 //!
@@ -66,7 +72,8 @@
 //! $ mkdir .cargo && edit .cargo/config && cat $_
 //! [target.riscv32imac-unknown-none-elf]
 //! rustflags = [
-//!   "-C", "link-arg=-Tlink.x"
+//!   "-C", "link-arg=-Tmemory.x",
+//!   "-C", "link-arg=-Tlink.x",
 //! ]
 //!
 //! [build]
@@ -124,14 +131,27 @@
 //!
 //! 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][2], but at a minimum you'll want to create two memory regions: one
-//! for Flash memory and another for RAM.
+//! [here][2], but at a minimum you'll want to create at least one memory region.
 //!
 //! [2]: https://sourceware.org/binutils/docs/ld/MEMORY.html
 //!
-//! 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.
+//! To support different relocation models (RAM-only, FLASH+RAM) multiple regions are used:
+//!
+//! - `REGION_TEXT` - for `.init`, `.trap` and `.text` sections
+//! - `REGION_RODATA` - for `.rodata` section and storing initial values for `.data` section
+//! - `REGION_DATA` - for `.data` section
+//! - `REGION_BSS` - for `.bss` section
+//! - `REGION_HEAP` - for the heap area
+//! - `REGION_STACK` - for hart stacks
+//!
+//! Specific aliases for these regions must be defined in `memory.x` file (see example below).
+//!
+//! ### `_stext`
+//!
+//! This symbol provides the loading address of `.text` section. This value can be changed
+//! to override the loading address of the firmware (for example, in case of bootloader present).
+//!
+//! If omitted this symbol value will default to `ORIGIN(REGION_TEXT)`.
 //!
 //! ### `_stack_start`
 //!
@@ -140,25 +160,49 @@
 //! 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).
 //!
-//! If omitted this symbol value will default to `ORIGIN(RAM) + LENGTH(RAM)`.
+//! In case of multiple harts present, this address defines the initial stack pointer for hart 0.
+//! Stack pointer for hart `N` is calculated as  `_stack_start - N * _hart_stack_size`.
+//!
+//! If omitted this symbol value will default to `ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)`.
 //!
 //! #### Example
 //!
 //! Allocating the call stack on a different RAM region.
 //!
-//! ```
+//! ``` text
 //! 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
+//!   L2_LIM : ORIGIN = 0x08000000, LENGTH = 1M
+//!   RAM : ORIGIN = 0x80000000, LENGTH = 16K
+//!   FLASH : ORIGIN = 0x20000000, LENGTH = 16M
 //! }
 //!
-//! _stack_start = ORIGIN(CCRAM) + LENGTH(CCRAM);
+//! REGION_ALIAS("REGION_TEXT", FLASH);
+//! REGION_ALIAS("REGION_RODATA", FLASH);
+//! REGION_ALIAS("REGION_DATA", RAM);
+//! REGION_ALIAS("REGION_BSS", RAM);
+//! REGION_ALIAS("REGION_HEAP", RAM);
+//! REGION_ALIAS("REGION_STACK", L2_LIM);
+//!
+//! _stack_start = ORIGIN(L2_LIM) + LENGTH(L2_LIM);
 //! ```
 //!
+//! ### `_max_hart_id`
+//!
+//! This symbol defines the maximum hart id suppoted. All harts with id
+//! greater than `_max_hart_id` will be redirected to `abort()`.
+//!
+//! This symbol is supposed to be redefined in platform support crates for
+//! multi-core targets.
+//!
+//! If omitted this symbol value will default to 0 (single core).
+//!
+//! ### `_hart_stack_size`
+//!
+//! This symbol defines stack area size for *one* hart.
+//!
+//! If omitted this symbol value will default to 2K.
+//!
 //! ### `_heap_size`
 //!
 //! This symbol provides the size of a heap region. The default value is 0. You can set `_heap_size`
@@ -172,7 +216,7 @@
 //!
 //! #### Example
 //!
-//! ```
+//! ``` no_run
 //! extern crate some_allocator;
 //!
 //! extern "C" {
@@ -189,13 +233,22 @@
 //! }
 //! ```
 //!
-//! ## `pre_init!`
+//! ### `_mp_hook`
+//!
+//! This function is called from all the harts and must return true only for one hart,
+//! which will perform memory initialization. For other harts it must return false
+//! and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt).
+//!
+//! This function can be redefined in the following way:
 //!
-//! A user-defined function can be run at the start of the reset handler, before RAM is
-//! initialized. The macro `pre_init!` can be called to set the function to be run. The function is
-//! intended to perform actions that cannot wait the time it takes for RAM to be initialized, such
-//! as disabling a watchdog. As the function is called before RAM is initialized, any access of
-//! static variables will result in undefined behavior.
+//! ``` no_run
+//! #[export_name = "_mp_hook"]
+//! pub extern "Rust" fn mp_hook() -> bool {
+//!    // ...
+//! }
+//! ```
+//!
+//! Default implementation of this function wakes hart 0 and busy-loops all the other harts.
 
 // NOTE: Adapted from cortex-m/src/lib.rs
 #![no_std]
@@ -208,7 +261,11 @@ extern crate r0;
 
 pub use macros::{entry, pre_init};
 
-use riscv::register::{mstatus, mtvec};
+use riscv::register::mstatus;
+
+#[export_name = "error: riscv-rt appears more than once in the dependency graph"]
+#[doc(hidden)]
+pub static __ONCE__: () = ();
 
 extern "C" {
     // Boundaries of the .bss section
@@ -221,9 +278,6 @@ extern "C" {
 
     // Initial values of the .data section (stored in Flash)
     static _sidata: u32;
-
-    // Address of _start_trap
-    static _start_trap: u32;
 }
 
 
@@ -240,18 +294,19 @@ pub unsafe extern "C" fn start_rust() -> ! {
 
         // This symbol will be provided by the user via `#[pre_init]`
         fn __pre_init();
+
+        fn _mp_hook() -> bool;
     }
 
-    __pre_init();
+    if _mp_hook() {
+        __pre_init();
 
-    r0::zero_bss(&mut _sbss, &mut _ebss);
-    r0::init_data(&mut _sdata, &mut _edata, &_sidata);
+        r0::zero_bss(&mut _sbss, &mut _ebss);
+        r0::init_data(&mut _sdata, &mut _edata, &_sidata);
+    }
 
     // TODO: Enable FPU when available
 
-    // Set mtvec to _start_trap
-    mtvec::write(&_start_trap as *const _ as usize, mtvec::TrapMode::Direct);
-
     main();
 }
 
@@ -279,10 +334,22 @@ pub extern "C" fn start_trap_rust() {
 }
 
 
-/// Default Trap Handler
+#[doc(hidden)]
 #[no_mangle]
 pub fn default_trap_handler() {}
 
 #[doc(hidden)]
 #[no_mangle]
 pub unsafe extern "Rust" fn default_pre_init() {}
+
+#[doc(hidden)]
+#[no_mangle]
+pub extern "Rust" fn default_mp_hook() -> bool {
+    use riscv::register::mhartid;
+    match mhartid::read() {
+        0 => true,
+        _ => loop {
+            unsafe { riscv::asm::wfi() }
+        },
+    }
+}