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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
//! Some helper macros to generates a NOR flash header ("flash control block"),
//! initial vector table, and small shim code that can be written to the start
//! of the NOR flash connected to FlexSPI0 on an i.MX RT500-series chip to deal
//! with its unusual requirements before jumping into a more normal-looking
//! embedded Rust application linked to somewhere else in memory -- typically
//! a later page or block of the same Flash memory, but that's not required.
//!
//! This series of chips has no built-in flash memory and so instead has a
//! boot ROM that probes various peripherals to try to find some external
//! memory containing a boot image. One option is a NOR flash connected to
//! FlexSPI0, but that requires some chip-specific header information in
//! the first page of flash that is intermingled with the initial vector
//! table, and is thus incompatible with the image layout typically generated
//! by the `cortex-m-rt` crate.
//!
//! To allow using `cortex-m-rt` in the normal way, this crate can help
//! generate a small stub image to write into the first page of memory,
//! separately from the main application, which then expects to find a
//! normal-looking `cortex-m-rt`-based application at some other memory
//! address and begins executing it.
//!
//! This does mean "wasting" at least a page of flash memory and having
//! a redundant extra vector table used only by the boot stub, but that's
//! typically a small price to pay for the convenience of meeting the
//! expectations of the embedded Rust ecosystem for Cortex-M-based platforms.
//!
//! # Bootstub Styles
//!
//! For flexibility for different application needs, this library offers
//! two different variations of the boot stub generator macro. Each
//! application must include exactly one call to exactly one of these.
//!
//! * [`bootstub_builtin`] is the most straightforward option, which embeds
//!   a bootstub directly inside the application and transfers control to
//!   the application code using the vector table generated by the
//!   `cortex-m-rt` linker script.
//!
//!     This approach provides the most "normal" development experience,
//!     but at the expense of slightly bloating your application image
//!     by embedding the boot stub directly into it.
//! * [`bootstub_standalone`] is a more specialized option which allows
//!   building an executable that is _only_ a boot stub, expecting to find
//!   the real application vector table at a fixed memory location.
//!
//!     This approach in theory allows flashing the bootstub image only
//!     once and then flashing normal application code with its vectors at
//!     the designated address as a separate operation. The bootstub and
//!     the application are independent from one another and can be updated
//!     separately.
//!
//! # Flash Control Block
//!
//! The RT500 boot ROM also requires the NOR flash to contain a
//! _Flash Control Block_, which is a data structure that both declares that
//! the flash memory is intended as boot media and helps the boot ROM to
//! configure the FlexSPI0 peripheral to read from it efficiently.
//!
//! If your flash does not include a flash control block then the boot ROM
//! will not consider the NOR flash as a candidate boot medium.
//!
//! Use [`fcb`] to declare a Flash Control Block, which should then be linked
//! at the appropriate location using your linker script.
#![no_std]

use core::arch::asm;

pub mod bootrom;

extern "C" {
    #[doc(hidden)]
    pub fn __mimxrt500_bootstub_main(app_vectors: *const u32);
    #[doc(hidden)]
    pub fn __mimxrt500_bootstub();
    #[doc(hidden)]
    fn __mimxrt500_bootstub_image_start();
    #[doc(hidden)]
    pub fn __vector_table();
}

/// Generates a bootstub intended for flashing separately from any real
/// application, which expects to find a vector table at a fixed location.
///
/// This allows a more advanced usage pattern where you can write a standalone
/// bootstub program and write it just once to the beginning of the flash
/// memory, and then use it for arbitrary applications written with their
/// vector tables at the designated location without them needing to include
/// any special boot stub code themselves.
///
/// ```
/// bootstub_standalone!(0x10000);
/// ```
///
/// A boot stub created in this way expects to find a suitable vector table
/// at the given address but does not include one itself. If you choose your
/// vector table address so that it's in a separate flash memory page than
/// the boot stub then you can re-flash the application without also
/// re-flashing the boot stub.
#[macro_export]
macro_rules! bootstub_standalone {
    ($app_vectors:literal) => {
        ::core::arch::global_asm!(
            ".cfi_sections .debug_frame",
            ".section .mimxrt500_bootstub.text, \"ax\"",
            ".global {entry}",
            ".type {entry},%function",
            ".thumb_func",
            ".cfi_startproc",
            "{entry}:",
            concat!("ldr r0,=", $app_vectors),
            "b {bootstub}",
            ".cfi_endproc",
            ".size {entry}, . - {entry}",
            entry = sym $crate::__mimxrt500_bootstub,
            bootstub = sym $crate::__mimxrt500_bootstub_main,
        );
    }
}

/// Generates a bootstub for inclusion as part of the flash image for an
/// application.
///
/// With no arguments the boot stub will try to automatically locate the
/// vector table generated by the `cortex-m-rt` crate's linker script. This
/// is a good option if you are intending to follow embedded Rust idiom for
/// your application's build process.
///
/// You can optionally provide an argument which refers to a static variable
/// that must be a valid ARMv8-M vector table. This macro cannot verify that
/// the given symbol _does_ match the processor's requirements for a vector
/// table, and so if you specify an unreasonable symbol the behavior will
/// be unsound.
#[macro_export]
macro_rules! bootstub_builtin {
    () => {
        ::core::arch::global_asm!(
            ".cfi_sections .debug_frame",
            ".section .mimxrt500_bootstub.text, \"ax\"",
            ".global {entry}",
            ".type {entry},%function",
            ".thumb_func",
            ".cfi_startproc",
            "{entry}:",
            "ldr r0,={vectors}",
            "b {bootstub}",
            ".cfi_endproc",
            ".size {entry}, . - {entry}",
            entry = sym $crate::__mimxrt500_bootstub,
            bootstub = sym $crate::__mimxrt500_bootstub_main,
            vectors = sym $crate::__vector_table,
        );
    };
    ($app_vectors:path) => {
        ::core::arch::global_asm!(
            ".cfi_sections .debug_frame",
            ".section .mimxrt500_bootstub.text, \"ax\"",
            ".global {entry}",
            ".type {entry},%function",
            ".thumb_func",
            ".cfi_startproc",
            "{entry}:",
            "ldr r0,={vectors}",
            "b {bootstub}",
            ".cfi_endproc",
            ".size {entry}, . - {entry}",
            entry = sym $crate::__mimxrt500_bootstub,
            bootstub = sym $crate::__mimxrt500_bootstub_main,
            vectors = sym $app_vectors,
        );
    };
}

/// Generates a Flash Control Block as a global static symbol.
///
/// The argument must be a value of type [`bootrom::FlexSpiNorFlashConfig`].
/// This macro will arrange for that value to be placed into the appropriate
/// section so that a correctly-written linker script will place it at the
/// location where the on-chip boot ROM expects to find it.
#[macro_export]
macro_rules! fcb {
    ($data:expr) => {
        #[doc(hidden)]
        #[link_section = ".mimxrt500_bootstub.fcb"]
        #[no_mangle]
        static __MIMXRT500_FCB: $crate::bootrom::FlexSpiNorFlashConfig = $data;
    };
}

::core::arch::global_asm!(
    ".cfi_sections .debug_frame",
    ".section .mimxrt500_bootstub.text, \"ax\"",
    ".global {bootstub}",
    ".type {bootstub},%function",
    ".thumb_func",
    ".cfi_startproc",
    "{bootstub}:",

    // Use the application's vector table
    "ldr r1, =0xe000ed08", // r1 points at VTOR
    "str r0, [r1]", // store app_vectors argument (r0) to VTOR (*r1)

    // Load application's initial stack pointer
    "ldr sp, [r0, #0]",

    // Jump to application's reset vector
    "ldr pc, [r0, #4]",

    "1:",
    "b 1b",
    ".cfi_endproc",
    ".size {bootstub}, . - {bootstub}",
    bootstub = sym __mimxrt500_bootstub_main,
);

#[link_section = ".mimxrt500_bootstub.text"]
extern "C" fn default_exception_handler() {
    loop {
        unsafe { asm!("wfi") };
    }
}

#[doc(hidden)]
pub union Vector {
    handler: unsafe extern "C" fn(),
    reserved: u32,
}

const DEFAULT_VECTOR: Vector = Vector {
    handler: default_exception_handler,
};
const RESERVED_VECTOR: Vector = Vector { reserved: 0 };

const IMGTYPE_PLAIN_NO_SECURE: u32 = 0x00004000;

#[doc(hidden)]
#[link_section = ".mimxrt500_bootstub.exceptions"]
#[no_mangle]
pub static __mimxrt500_bootstub_exceptions: [Vector; 16] = [
    // Initial stack pointer is irrelevant because we don't use the stack,
    // but the boot ROM seems to verify that this points to a reasonable
    // address in RAM, so we'll just arbitrarily choose one.
    Vector { reserved: 0x5000 },
    // Reset vector is the generated boot stub
    Vector {
        handler: __mimxrt500_bootstub,
    },
    // NMI Exception
    DEFAULT_VECTOR,
    // HardFault Exception
    DEFAULT_VECTOR,
    // MemManage Exception
    DEFAULT_VECTOR,
    // BusFault Exception
    DEFAULT_VECTOR,
    // UsageFault Exception
    DEFAULT_VECTOR,
    // SecureFault Exception
    DEFAULT_VECTOR,
    // Entry 8 is used by the RT500 boot ROM as the image size, which
    // isn't really relevant here because we're not using the boot ROM's
    // checksum and copy-to-RAM features. We'll just claim that the
    // vector table is the entirety of the image.
    Vector { reserved: 256 * 4 },
    // Entry 9 is used by the RT500 boot ROM as the image type.
    Vector {
        reserved: IMGTYPE_PLAIN_NO_SECURE,
    },
    // Reserved entry
    RESERVED_VECTOR,
    // SVCall Exception
    DEFAULT_VECTOR,
    // Debug Monitor Exception
    DEFAULT_VECTOR,
    // Entry 13 is used by the RT500 boot ROM as the image load address,
    // which must point to the start of this vector table for a flash XIP
    // image.
    Vector {
        handler: __mimxrt500_bootstub_image_start,
    },
    // PendSV Exception
    DEFAULT_VECTOR,
    // SysTick Exception.
    DEFAULT_VECTOR,
];