1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/***********************************************************************************************************************
 * Copyright (c) 2019 by the authors
 *
 * Author: André Borrmann
 * License: Apache License 2.0
 **********************************************************************************************************************/
#![doc(html_root_url = "https://docs.rs/ruspiro-boot/0.3.0")]
#![no_std]
#![feature(asm, lang_items, linkage)]

//! # RusPiRo Boot Strapping for Raspberry Pi
//!
//! This crates provides the startup routines that are needed to be run from a baremetal kernel on
//! the RaspberryPi before execution could be handed over to Rust code.
//!
//! # Usage
//!
//! Put the following code into your main rustfile of the binary that should be build for the
//! Raspberry Pi:
//! ```ignore
//! #[macro_use]
//! extern crate ruspiro_boot;
//!
//! come_alive_with!(alive);
//! run_with!(running);
//!
//! fn alive(core: u32) {
//!     // do one-time initialization here
//! }
//!
//! fn running(core: u32) -> ! {
//!     // do any processing here and ensure you never return from this function
//!     loop {}
//! }
//! ```
//!
//! The bootstrapper requires specific symbols to be known to the linker to be able to build the
//! final binary. To use the linker script that is provided as part of this crate in
//! your own rust binary crate you could either copy them manually from the git repository based on
//! your desired target architecture for the build:
//! [aarch32 linker script](https://github.com/RusPiRo/ruspiro-boot/blob/v0.3.0/link32.ld)
//! [aarch64 linker script](https://github.com/RusPiRo/ruspiro-boot/blob/v0.3.0/link64.ld)
//! 
//! # Features
//!
//! - `with_panic` will ensure that a default panic handler is implemented.
//! - `singlecore` enforces the compilation of the single core boot sequence. Only the main core 0 is then running.
//! - `ruspiro_pi3` is passed to dependent crates to properly build them for the desired Raspberry Pi version
//!
//! To successfully build a bare metal binary using this crate for the boot strapping part it is 
//! **highly recomended** to use the linker script provided by this crate. Based on the target
//! architecture to be built it is either [link32.ld](link32.ld) or [link64.ld](link64.ld). To
//! conviniently refer to the linker scripts contained in this crate it's recommended to use a 
//! specific build script in your project that copies the required file to your current project 
//! folder and could then be referred to with the `RUSTFLAG` parameter `-C link-arg=-T./link<aarch>.ld`.
//! The build script is a simple `build.rs` rust file in your project root with the following
//! contents:
//! ```no_run
//! use std::{env, fs, path::Path};
//! 
//! fn main() {
//!     // copy the linker script from the boot crate to the current directory
//!     // so it will be invoked by the linker
//!     let ld_source = env::var_os("DEP_RUSPIRO_BOOT_LINKERSCRIPT")
//!         .expect("error in ruspiro build, `ruspiro-boot` not a dependency?");
//!     let src_file = Path::new(&ld_source);
//!     let trg_file = format!(
//!         "{}/{}",
//!         env::current_dir().unwrap().display(),
//!         src_file.file_name().unwrap().to_str().unwrap()
//!     );
//!     println!("Copy linker script from {:?}, to {:?}", src_file, trg_file);
//!     fs::copy(src_file, trg_file).unwrap();
//! }
//! ```
//! 
//! To get started you could check out the template projects [here](https://www.github.com/RusPiRo/ruspiro_templates)
//! 

pub mod macros;
pub use self::macros::*;

#[cfg_attr(target_arch = "aarch64", path = "mmu64.rs")]
#[cfg_attr(target_arch = "arm", path = "mmu32.rs")]
mod mmu;

#[cfg(not(test))]
mod panic;
#[cfg(not(test))]
mod stubs;

#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
use ruspiro_cache as cache;
use ruspiro_interrupt::IRQ_MANAGER;
use ruspiro_timer as timer;
use ruspiro_uart::Uart1;
use ruspiro_console::*;

extern "C" {
    fn __kernel_startup(core: u32);
    fn __kernel_run(core: u32) -> !;
    fn __boot();
}

/// Entry point that is called by the bootstrapping code.
/// From here we will branch into the kernel code provided by the user of this crate.
/// To conviniently provide those entry points the crate user should use the respective macros
/// `come_alive_with!` and `run_with!`. This entry point is assumed to be always called
/// in EL1(aarch64) or SVC(aarch32) mode
///
#[export_name = "__rust_entry"]
fn __rust_entry(core: u32) -> ! {
    // very first thing is to setup the MMU which allows us to
    // use atomic operations in the upcomming initialization
    #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
    mmu::initialize_mmu(core);

    // special additional setup might be done on the main core only
    if core == 0 {
        // first thing we would like to do is to let the outside world know that we are booting
        // so if this is core 0 we initialze the uart1 interface with default settings and print some
        // string
        let mut uart = Uart1::new();

        let _ = uart.initialize(250_000_000, 115_200);
        CONSOLE.take_for(|console| console.replace(uart));

        #[cfg(target_arch = "aarch64")]
        println!("\r\n########## RusPiRo ----- Bootstrapper v0.3 @ Aarch64 ----- ##########");
        #[cfg(target_arch = "arm")]
        println!("\r\n########## RusPiRo ----- Bootstrapper v0.3 @ Aarch32 ----- ##########");

        // do some arbitrary sleep here to let the uart send the initial greetings before running
        // the kernel, which may initialize the UART for it's own purpose and this would break
        // this transfer...
        timer::sleep(10000);

        // now initialize the interrupt manager
        IRQ_MANAGER.take_for(|irq_mgr| irq_mgr.initialize());
    }

    // now follows the configuration that is needed to be done by all cores
    // TODO: is there something we need to prepare ?

    // now that the initialization was done we can jump into the "application"
    // specific initialization
    #[cfg(not(test))]
    unsafe {
        __kernel_startup(core)
    }

    // once the one-time startup of this core has been done kickoff any other core
    #[cfg(all(
        any(target_arch = "arm", target_arch = "aarch64"),
        not(feature = "singlecore")
    ))]
    kickoff_next_core(core);

    // after the one time setup of the "application" enter the processing loop
    #[cfg(not(test))]
    unsafe {
        __kernel_run(core)
    }

    #[cfg(test)]
    loop {}
}

#[cfg(all(target_arch = "arm", not(feature = "singlecore")))]
fn kickoff_next_core(core: u32) {
    // kicking of another core in arch32 means, writing the jump address for this
    // core into it's specific mailbox
    let jump_store: u64 = match core {
        0 => 0x4000_009C, // write start address to core 1 mailbox 3
        1 => 0x4000_00AC, // write start address to core 2 mailbox 3
        2 => 0x4000_00BC, // write start address to core 3 mailbox 3
        _ => return,
    };
    unsafe {
        core::ptr::write_volatile(jump_store as *mut u32, __boot as *const () as u32);
        asm!("sev"); // trigger an event to wake up the sleeping cores
    }
}

#[cfg(all(target_arch = "aarch64", not(feature = "singlecore")))]
fn kickoff_next_core(core: u32) {
    // kicking of another core in arch64 means, writing the jump address for this
    // core into a specific memory location
    let jump_store: u64 = match core {
        0 => 0xe0,
        1 => 0xe8,
        2 => 0xf0,
        _ => return,
    };
    unsafe {
        core::ptr::write_volatile(jump_store as *mut u64, 0x80000); //__boot as *const () as u64);
                                                                    // as this core may have caches enabled, clean/invalidate so the other core
                                                                    // sees the correct data on memory and the write does not only hit the cache
        cache::cleaninvalidate();
        asm!("sev"); // trigger an event to wake up the sleeping cores
    }
}