aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan McIntyre <ianpmcintyre@gmail.com>2022-08-02 06:21:12 -0400
committerIan McIntyre <ianpmcintyre@gmail.com>2022-12-01 20:21:05 -0500
commitc7a9b9f3d4b9e71303c7b988d2bd916c2e4df9bc (patch)
tree6d41ea7e433cac328fa165d45d1bc0cd71a1bf8f
First commit
-rw-r--r--.cargo/config.toml4
-rw-r--r--.github/workflows/rust.yaml92
-rw-r--r--.gitignore4
-rw-r--r--Cargo.toml59
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT21
-rw-r--r--README.md53
-rw-r--r--board/Cargo.toml47
-rw-r--r--board/README.md122
-rw-r--r--board/build.rs53
-rw-r--r--board/src/imxrt1010evk.rs36
-rw-r--r--board/src/imxrt1170evk_cm7.rs38
-rw-r--r--board/src/lib.rs107
-rw-r--r--board/src/shared/imxrt10xx.rs35
-rw-r--r--board/src/shared/imxrt11xx.rs52
-rw-r--r--board/src/teensy4.rs37
-rw-r--r--cmds.gdb4
-rw-r--r--examples/blink-blocking.rs18
-rw-r--r--examples/blink-rtic.rs42
-rw-r--r--src/host.rs822
-rw-r--r--src/host/imxrt-boot-header.x73
-rw-r--r--src/host/imxrt-link.x198
-rw-r--r--src/lib.rs167
-rw-r--r--src/target.rs147
-rw-r--r--tests/inspect_elf.rs445
25 files changed, 2877 insertions, 0 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..3c0580e
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,4 @@
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+rustflags = [
+ "-C", "link-arg=-Timxrt-link.x",
+]
diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml
new file mode 100644
index 0000000..2e03ee4
--- /dev/null
+++ b/.github/workflows/rust.yaml
@@ -0,0 +1,92 @@
+name: Rust CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ format:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ components: rustfmt
+ - name: Check formatting
+ uses: actions-rs/cargo@v1
+ with:
+ command: fmt
+ args: --verbose --all -- --check
+
+ clippy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ components: clippy
+ - name: Lint the host API.
+ uses: actions-rs/clippy-check@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ args: --package=imxrt-rt --tests
+
+ tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ target: thumbv7em-none-eabihf
+ - name: Run doc tests
+ uses: actions-rs/cargo@v1
+ with:
+ command: test
+ args: --doc
+ - name: Run automated tests
+ uses: actions-rs/cargo@v1
+ with:
+ command: test
+ args: --tests -- --include-ignored
+
+ boards:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ board: [ teensy4, imxrt1010evk, imxrt1170evk-cm7 ]
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ target: thumbv7em-none-eabihf
+ - name: Lint the RTIC example for ${{ matrix.board }}
+ uses: actions-rs/clippy-check@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ args: --example=blink-rtic --target=thumbv7em-none-eabihf --features=board/${{ matrix.board }},board/rtic -- -D warnings
+ - name: Lint the blocking example ${{ matrix.board }}
+ uses: actions-rs/clippy-check@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ args: --example=blink-blocking --target=thumbv7em-none-eabihf --features=board/${{ matrix.board }} -- -D warnings
+
+ docs:
+ runs-on: ubuntu-latest
+ env:
+ RUSTDOCFLAGS: -D warnings
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ - name: Check host documentation
+ uses: actions-rs/cargo@v1
+ with:
+ command: rustdoc
+ args: --package=imxrt-rt
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a503c06
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target
+/Cargo.lock
+*.hex
+.vscode/
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3fd0273
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,59 @@
+[package]
+name = "imxrt-rt"
+version = "0.1.0"
+edition = "2021"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/imxrt-rs/imxrt-rt"
+description = "Startup and runtime support for i.MX RT processors."
+categories = [
+ "embedded",
+ "hardware-support",
+ "no-std",
+]
+
+[features]
+device = ["cortex-m-rt/device"]
+
+[dependencies]
+cfg-if = "1.0"
+
+[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies]
+cortex-m-rt = { version = "=0.7.1" }
+
+[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dev-dependencies]
+board = { path = "board" }
+cortex-m-rtic = { version = "1.0" }
+
+[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dev-dependencies]
+goblin = "0.5"
+
+[[example]]
+name = "blink-rtic"
+required-features = ["board/rtic"]
+
+[workspace]
+members = [
+ "board",
+]
+
+[profile.dev]
+opt-level = 0
+lto = "off"
+panic = "abort"
+
+[profile.release]
+opt-level = "s"
+lto = "fat"
+panic = "abort"
+codegen-units = 1
+
+[profile.dev.build-override]
+opt-level = 0
+codegen-units = 256
+
+[profile.release.build-override]
+opt-level = 0
+codegen-units = 256
+
+[patch.crates-io.imxrt-ral]
+git = "https://github.com/mciantyre/imxrt-ral"
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..2495d03
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Ian McIntyre
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..80a36be
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+# imxrt-rt
+
+Runtime and startup support for i.MX RT processors.
+
+This crate builds on `cortex-m-rt` and adds support for i.MX RT
+processors. Using this runtime crate, you can specify FlexRAM sizes and
+section allocations, then use it to boot your i.MX RT processor.
+
+The crate achieves this with
+
+- a build-time API to define the memory map.
+- a runtime library to configure the embedded processor.
+
+To learn how to use this crate in your firmware, see the crate
+documentation. To try the runtime on hardware, see [the `board`
+documentation].
+
+ [the `board` documentation]: board/README.md
+
+## Development
+
+Run automated tests like this:
+
+ cargo test --tests
+ cargo test --doc
+ cargo test --tests -- --ignored
+
+If you have `pyOCD` available, you can check the effects of the runtime
+initialization routine with GDB:
+
+ pyocd gdb --target=$YOUR_TARGET
+ arm-none-eabi-gdb < cmds.gdb
+
+Make sure that the register values make sense for your target.
+
+## License
+
+Licensed under either of
+
+- [Apache License, Version 2.0] ([LICENSE-APACHE])
+- [MIT License] ([LICENSE-MIT])
+
+at your option.
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the Apache-2.0
+license, shall be dual licensed as above, without any additional terms
+or conditions.
+
+ [Apache License, Version 2.0]: http://www.apache.org/licenses/LICENSE-2.0
+ [LICENSE-APACHE]: ./LICENSE-APACHE
+ [MIT License]: http://opensource.org/licenses/MIT
+ [LICENSE-MIT]: ./LICENSE-MIT
diff --git a/board/Cargo.toml b/board/Cargo.toml
new file mode 100644
index 0000000..21defd4
--- /dev/null
+++ b/board/Cargo.toml
@@ -0,0 +1,47 @@
+[package]
+name = "board"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies.cfg-if]
+version = "1.0"
+
+[dependencies.imxrt-ral]
+version = "0.5"
+
+[dependencies.imxrt-rt]
+path = ".."
+
+[build-dependencies.imxrt-rt]
+path = ".."
+
+[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies]
+teensy4-fcb = { version = "0.3", optional = true }
+teensy4-panic = { version = "0.2", optional = true }
+
+imxrt1010evk-fcb = { version = "0.1", optional = true }
+imxrt1170evk-fcb = { version = "0.1", optional = true }
+rtt-target = { version = "0.3", optional = true, features = ["cortex-m"] }
+panic-rtt-target = { version = "0.1", optional = true, features = ["cortex-m"] }
+
+[features]
+rtic = []
+# Begin board features.
+teensy4 = [
+ "imxrt-ral/imxrt1062",
+ "dep:teensy4-fcb",
+ "dep:teensy4-panic",
+]
+imxrt1010evk = [
+ "imxrt-ral/imxrt1011",
+ "dep:imxrt1010evk-fcb",
+ "dep:rtt-target",
+ "dep:panic-rtt-target",
+]
+imxrt1170evk-cm7 = [
+ "imxrt-ral/imxrt1176_cm7",
+ "dep:imxrt1170evk-fcb",
+ "dep:rtt-target",
+ "dep:panic-rtt-target",
+]
diff --git a/board/README.md b/board/README.md
new file mode 100644
index 0000000..bd4a6e3
--- /dev/null
+++ b/board/README.md
@@ -0,0 +1,122 @@
+`board` provides a thin board support package for `imxrt-rt`. The
+package provides cross-board compatibility for all `imxrt-rt` hardware
+examples. It supports `imxrt-rt` development and testing, and is not
+intended as a general BSP.
+
+`board` supports
+
+- Teensy 4.0 and Teensy 4.1 boards with the `teensy4` feature.
+- the IMXRT1010EVK board with the `imxrt1010evk` feature.
+- the Cortex M7 on the IMXRT1170EVK with the `imxrt1170evk-cm7`
+ feature.
+
+When using any NXP EVK, make sure that your boot device is FlexSPI.
+Consult your board's hardware user guide for more information.
+
+## Board configurations
+
+The examples in this repository are very basic. They demonstrate a
+working runtime by blinking an LED. They also configure timer interrupts
+to show that the vector table is placed and registered correctly. They
+only use `imxrt-ral` to access registers.
+
+Boards simply specify an LED. See the relevant module in `board/src/`
+for more information.
+
+`build.rs` configures the runtime for each board. You can change this to
+explore different runtime configurations.
+
+## Building hardware examples
+
+Hardware examples for `imxrt-rt` depend on `board` and a board
+selection. This section describes how to build an example for your
+board. It focuses on building examples for the Teensy 4, but the concept
+generalizes for all supported boards.
+
+To build the `blink-blocking` example for a Teensy 4, run the command
+from the repo's root:
+
+ cargo build --example=blink-blocking --features=board/teensy4 --target=thumbv7em-none-eabihf
+
+Generally, you select the example with `--example`, and specify the
+board with `--features=board/[your-board]`. To build the same example
+for the IMXRT1010EVK, change `--features=board/teensy4` to
+`--features=board/imxrt1010evk`.
+
+To build an RTIC-based example, enable the `rtic` feature of `board`.
+
+Artifacts are available under
+`target/thumbv7em-none-eabihf/[debug|release]/examples`. Keep this in
+mind when flashing your board.
+
+## Flashing hardware examples
+
+The tools required to flash an example depend on the board you're using.
+This section recommends tooling to flash hardware examples on your
+board.
+
+### NXP IMXRT EVKs
+
+If you're using an NXP IMXRT EVK, you can use any of the following to
+flash your board.
+
+- [`pyOCD`] supports all i.MX RT 10xx and 11xx boards.
+- [`probe-rs` tools] only support i.MX RT 10xx boards. These tools
+ include
+ - [`probe-run`]
+ - [`cargo-flash`]
+ - [`cargo-embed`]
+
+See each tool's documentation to understand its usage. To make some
+tooling integration easier, see the Tips and Tricks section near the end
+of this document.
+
+ [`pyOCD`]: https://pyocd.io
+ [`probe-rs` tools]: https://probe.rs
+ [`probe-run`]: https://github.com/knurling-rs/probe-run
+ [`cargo-flash`]: https://github.com/probe-rs/cargo-flash
+ [`cargo-embed`]: https://github.com/probe-rs/cargo-embed
+
+### Teensy 4
+
+If you're using a Teensy 4 board, you'll need all of the following:
+
+- An `objcopy` capable of transforming ELF files into Intel HEX.
+ Consider using `rust-objcopy` provided by [`cargo-binutils`]. The
+ rest of this documentation assumes you're using `cargo-binutils`.
+- Either a build of [`teensy_loader_cli`], or the [Teensy Loader
+ Application]. The latter is available with the Teensyduino add-ons.
+
+After building your example, use `rust-objcopy` to convert the program
+into HEX. For the `blink-blocking` example above, that command resembles
+
+ rust-objcopy -O ihex target/thumbv7em-none-eabihf/debug/examples/blink-blocking blink-blocking.hex
+
+Finally, load the HEX file onto your board using your preferred loader.
+
+ [`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils
+ [`teensy_loader_cli`]: https://github.com/PaulStoffregen/teensy_loader_cli
+ [Teensy Loader Application]: https://www.pjrc.com/teensy/loader.html
+
+## Tips and tricks
+
+If you're using `probe-run` or `pyOCD` to flash an EVK, use the tool as
+a runner. See the [Cargo Configuration] documentation for more
+information. Please do not check your runner setting into the
+repository; consider using environment variables or hierarchical
+configuration files to configure your runner and any other useful
+command aliases.
+
+ [Cargo Configuration]: https://doc.rust-lang.org/cargo/reference/config.html
+
+## Adding a new board
+
+Define a new module in `board/src/` that describes your board's LED. Use
+the existing examples as your guide.
+
+Add a new feature to `board/Cargo.toml` for your board. Link any
+additional dependencies for your board, like FCB crates and panic
+handlers. If an FCB crate does not exist for your board, you can define
+the FCB within your newly-added module.
+
+Update `board/build.rs` to configure a runtime for your chip.
diff --git a/board/build.rs b/board/build.rs
new file mode 100644
index 0000000..9ee64e8
--- /dev/null
+++ b/board/build.rs
@@ -0,0 +1,53 @@
+use std::{collections::HashSet, env};
+
+fn extract_features() -> HashSet<String> {
+ env::vars()
+ .map(|(k, _)| k)
+ .flat_map(|feat| feat.strip_prefix("CARGO_FEATURE_").map(str::to_lowercase))
+ .collect()
+}
+
+/// Configures the runtime for a variety of boards.
+///
+/// Note that some automated tests may check these runtimes. Feel free to change
+/// values and observe how they might affect the tests.
+fn main() {
+ let features = extract_features();
+ for feature in features {
+ match feature.as_str() {
+ "teensy4" => {
+ imxrt_rt::RuntimeBuilder::from_flexspi(imxrt_rt::Family::Imxrt1060, 1984 * 1024)
+ .flexram_banks(imxrt_rt::FlexRamBanks {
+ ocram: 0,
+ dtcm: 12,
+ itcm: 4,
+ })
+ .heap_size(1024)
+ .text(imxrt_rt::Memory::Flash)
+ .rodata(imxrt_rt::Memory::Dtcm)
+ .data(imxrt_rt::Memory::Dtcm)
+ .bss(imxrt_rt::Memory::Dtcm)
+ .uninit(imxrt_rt::Memory::Dtcm)
+ .build()
+ .unwrap()
+ }
+ "imxrt1010evk" => imxrt_rt::RuntimeBuilder::from_flexspi(
+ imxrt_rt::Family::Imxrt1010,
+ 16 * 1024 * 1024,
+ )
+ .heap_size(1024)
+ .rodata(imxrt_rt::Memory::Flash)
+ .build()
+ .unwrap(),
+ "imxrt1170evk_cm7" => imxrt_rt::RuntimeBuilder::from_flexspi(
+ imxrt_rt::Family::Imxrt1170,
+ 16 * 1024 * 1024,
+ )
+ .rodata(imxrt_rt::Memory::Dtcm)
+ .build()
+ .unwrap(),
+ _ => continue,
+ }
+ break;
+ }
+}
diff --git a/board/src/imxrt1010evk.rs b/board/src/imxrt1010evk.rs
new file mode 100644
index 0000000..9a596e9
--- /dev/null
+++ b/board/src/imxrt1010evk.rs
@@ -0,0 +1,36 @@
+//! iMXRT1010EVK support.
+
+use crate::ral;
+
+#[cfg(target_arch = "arm")]
+use imxrt1010evk_fcb as _;
+#[cfg(target_arch = "arm")]
+use panic_rtt_target as _;
+
+const LED_OFFSET: u32 = 11;
+
+pub mod rtic_support {
+ pub use crate::ral::*;
+}
+
+/// Prepare the board for the examples.
+///
+/// Call this first. Panics if something went wrong.
+pub fn prepare(timer_delay_microseconds: u32) -> Option<crate::Resources> {
+ #[cfg(target_arch = "arm")]
+ rtt_target::rtt_init_print!();
+
+ let iomuxc = unsafe { ral::iomuxc::IOMUXC::instance() };
+ // Set the GPIO pad to a GPIO function (ALT 5)
+ ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_11, 5);
+ // Increase drive strength, but leave other fields at their current value...
+ ral::modify_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_11, DSE: DSE_7_R0_7);
+
+ let pit = crate::prepare_pit(timer_delay_microseconds)?;
+
+ let gpio1 = unsafe { ral::gpio::GPIO1::instance() };
+ Some(crate::Resources {
+ led: crate::Led::new(LED_OFFSET, &gpio1),
+ pit,
+ })
+}
diff --git a/board/src/imxrt1170evk_cm7.rs b/board/src/imxrt1170evk_cm7.rs
new file mode 100644
index 0000000..7d966eb
--- /dev/null
+++ b/board/src/imxrt1170evk_cm7.rs
@@ -0,0 +1,38 @@
+//! Support for booting the Cortex M7 on the i.MX RT 1170 EVK.
+
+use crate::ral;
+
+#[cfg(target_arch = "arm")]
+use imxrt1170evk_fcb as _;
+#[cfg(target_arch = "arm")]
+use panic_rtt_target as _;
+
+const LED_OFFSET: u32 = 3;
+
+pub mod rtic_support {
+ pub use crate::ral::NVIC_PRIO_BITS;
+ #[allow(non_snake_case)] // For RTIC trickery...
+ pub mod Interrupt {
+ pub const PIT: crate::ral::Interrupt = crate::ral::Interrupt::PIT1;
+ }
+ pub use Interrupt as interrupt;
+}
+
+pub fn prepare(timer_delay_microseconds: u32) -> Option<crate::Resources> {
+ #[cfg(target_arch = "arm")]
+ rtt_target::rtt_init_print!();
+
+ let iomuxc = unsafe { ral::iomuxc::IOMUXC::instance() };
+ ral::modify_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_AD_04, MUX_MODE: 5);
+
+ let ccm = unsafe { ral::ccm::CCM::instance() };
+ // Enable LPCG for GPIOs.
+ ral::write_reg!(ral::ccm, ccm, LPCG51_DIRECT, 1);
+
+ let gpio = unsafe { ral::gpio::GPIO3::instance() };
+ let pit = crate::prepare_pit(timer_delay_microseconds)?;
+ Some(crate::Resources {
+ pit,
+ led: crate::Led::new(LED_OFFSET, &gpio),
+ })
+}
diff --git a/board/src/lib.rs b/board/src/lib.rs
new file mode 100644
index 0000000..24d746a
--- /dev/null
+++ b/board/src/lib.rs
@@ -0,0 +1,107 @@
+//! A very simple, multi-board BSP for imxrt-rt-support examples.
+#![no_std]
+
+use imxrt_ral as ral;
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "teensy4")] {
+ mod shared { pub mod imxrt10xx; }
+ use shared::imxrt10xx::prepare_pit;
+
+ mod teensy4;
+ pub use teensy4::*;
+ } else if #[cfg(feature = "imxrt1010evk")] {
+ mod shared { pub mod imxrt10xx; }
+ use shared::imxrt10xx::prepare_pit;
+
+ mod imxrt1010evk;
+ pub use imxrt1010evk::*;
+ } else if #[cfg(feature = "imxrt1170evk-cm7")] {
+ mod shared { pub mod imxrt11xx; }
+ use shared::imxrt11xx::prepare_pit;
+
+ mod imxrt1170evk_cm7;
+ pub use imxrt1170evk_cm7::*;
+ } else {
+ compile_error!("No board feature selected!");
+ }
+}
+
+pub struct Pit(&'static ral::pit::RegisterBlock);
+
+impl Pit {
+ fn new(pit: &ral::pit::RegisterBlock, timer_delay_microseconds: u32) -> Self {
+ // Disable the PIT, just in case it was used by the boot ROM
+ ral::write_reg!(ral::pit, pit, MCR, MDIS: 1);
+ let timer = &pit.TIMER[0];
+ // Reset channel 0 control; we'll use channel 0 for our timer
+ ral::write_reg!(ral::pit::timer, timer, TCTRL, 0);
+ // Set the counter value
+ ral::write_reg!(ral::pit::timer, timer, LDVAL, timer_delay_microseconds);
+ // Enable the PIT timer
+ ral::modify_reg!(ral::pit, pit, MCR, MDIS: 0);
+ Self(unsafe { core::mem::transmute(pit) })
+ }
+ pub fn blocking_delay(&mut self) {
+ let timer = &self.0.TIMER[0];
+ // Start counting!
+ ral::write_reg!(ral::pit::timer, timer, TCTRL, TEN: 1);
+ // Are we done?
+ while ral::read_reg!(ral::pit::timer, timer, TFLG, TIF == 0) {}
+ // We're done; clear the flag
+ ral::write_reg!(ral::pit::timer, timer, TFLG, TIF: 1);
+ // Turn off the timer
+ ral::write_reg!(ral::pit::timer, timer, TCTRL, TEN: 0);
+ }
+ pub fn loop_with_interrupts(&mut self) {
+ let timer = &self.0.TIMER[0];
+ // Enable interrupts and start counting
+ ral::write_reg!(ral::pit::timer, timer, TCTRL, TIE: 1);
+ ral::modify_reg!(ral::pit::timer, timer, TCTRL, TEN: 1);
+ }
+ pub fn clear_interrupts(&mut self) {
+ let timer = &self.0.TIMER[0];
+ while ral::read_reg!(ral::pit::timer, timer, TFLG, TIF == 1) {
+ ral::write_reg!(ral::pit::timer, timer, TFLG, TIF: 1);
+ }
+ }
+}
+
+unsafe impl Send for Pit {}
+
+pub struct Led {
+ offset: u32,
+ port: &'static ral::gpio::RegisterBlock,
+}
+
+impl Led {
+ fn new(offset: u32, port: &ral::gpio::RegisterBlock) -> Self {
+ let led = Led {
+ offset,
+ port: unsafe { core::mem::transmute(port) },
+ };
+ ral::modify_reg!(ral::gpio, port, GDIR, |gdir| gdir | led.mask());
+ led
+ }
+ fn mask(&self) -> u32 {
+ 1 << self.offset
+ }
+ pub fn set(&self) {
+ ral::write_reg!(ral::gpio, self.port, DR_SET, self.mask());
+ }
+
+ pub fn clear(&self) {
+ ral::write_reg!(ral::gpio, self.port, DR_CLEAR, self.mask());
+ }
+
+ pub fn toggle(&self) {
+ ral::write_reg!(ral::gpio, self.port, DR_TOGGLE, self.mask());
+ }
+}
+
+unsafe impl Send for Led {}
+
+pub struct Resources {
+ pub led: crate::Led,
+ pub pit: crate::Pit,
+}
diff --git a/board/src/shared/imxrt10xx.rs b/board/src/shared/imxrt10xx.rs
new file mode 100644
index 0000000..5ba44eb
--- /dev/null
+++ b/board/src/shared/imxrt10xx.rs
@@ -0,0 +1,35 @@
+//! Code shared across all i.MX RT 10xx chips.
+use crate::ral;
+
+pub(crate) fn prepare_pit(timer_delay_microseconds: u32) -> Option<crate::Pit> {
+ #[cfg(feature = "rtic")]
+ {
+ extern "C" {
+ // Not actually mut in cortex-m. But, no one is reading it...
+ static __INTERRUPTS: [core::cell::UnsafeCell<unsafe extern "C" fn()>; 240];
+ fn PIT();
+ }
+ unsafe {
+ __INTERRUPTS[crate::ral::interrupt::PIT as usize]
+ .get()
+ .write_volatile(PIT);
+ }
+ }
+ let ccm = unsafe { ral::ccm::CCM::instance() };
+ // Disable the PIT clock gate while we change the clock...
+ ral::modify_reg!(ral::ccm, ccm, CCGR1, CG6: 0b00);
+ // Set the periodic clock divider, selection.
+ // 24MHz crystal oscillator, divided by 24 == 1MHz PIT clock
+ ral::modify_reg!(
+ ral::ccm,
+ ccm,
+ CSCMR1,
+ PERCLK_PODF: DIVIDE_24,
+ PERCLK_CLK_SEL: PERCLK_CLK_SEL_1 // Oscillator clock
+ );
+ // Re-enable PIT clock
+ ral::modify_reg!(ral::ccm, ccm, CCGR1, CG6: 0b11);
+
+ let pit = unsafe { ral::pit::PIT::instance() };
+ Some(crate::Pit::new(&pit, timer_delay_microseconds))
+}
diff --git a/board/src/shared/imxrt11xx.rs b/board/src/shared/imxrt11xx.rs
new file mode 100644
index 0000000..e0d1074
--- /dev/null
+++ b/board/src/shared/imxrt11xx.rs
@@ -0,0 +1,52 @@
+//! Code shared across all i.MX RT 11xx chips.
+
+use crate::ral;
+
+pub(crate) fn prepare_pit(timer_delay_microseconds: u32) -> Option<crate::Pit> {
+ #[cfg(feature = "rtic")]
+ {
+ extern "C" {
+ // Not actually mut in cortex-m. But, no one is reading it...
+ static __INTERRUPTS: [core::cell::UnsafeCell<unsafe extern "C" fn()>; 240];
+ fn PIT();
+ }
+ unsafe {
+ __INTERRUPTS[crate::ral::Interrupt::PIT1 as usize]
+ .get()
+ .write_volatile(PIT);
+ }
+ }
+
+ let ccm = unsafe { ral::ccm::CCM::instance() };
+
+ // Change the bus clock to the 24 MHz XTAL.
+ // Wouldn't recommend doing this in a real system,
+ // since the bus clock is running rather slowly.
+ // But, it's good enough for a demo, and it lets us match
+ // the behaviors of the 10xx examples.
+ //
+ // If we decrease the bus speed too much, we seem to reach a condition
+ // where we can't re-flash the device. Haven't dug too deeply; only
+ // observed that keeping the bus clock faster lets us flash more reliably,
+ // at least with pyOCD.
+ let clock_root_2 = &ccm.CLOCK_ROOT[2];
+ ral::modify_reg!(ral::ccm::clockroot, clock_root_2, CLOCK_ROOT_CONTROL, MUX: 0b001, DIV: 0);
+ while ral::read_reg!(
+ ral::ccm::clockroot,
+ clock_root_2,
+ CLOCK_ROOT_STATUS0,
+ CHANGING == 1
+ ) {}
+
+ // Enable the clock gate to PIT1.
+ ral::write_reg!(ral::ccm, ccm, LPCG61_DIRECT, 1);
+
+ let pit = unsafe { ral::pit::PIT1::instance() };
+ // 24x scaling accounts for the 24x faster PIT clock.
+ // Looks like it's blinking at 1Hz, but I'm not pulling out
+ // my scope or stopwatch or anything.
+ Some(crate::Pit::new(
+ &pit,
+ timer_delay_microseconds.saturating_mul(24),
+ ))
+}
diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs
new file mode 100644
index 0000000..28587cd
--- /dev/null
+++ b/board/src/teensy4.rs
@@ -0,0 +1,37 @@
+//! Teensy4 support.
+
+use crate::ral;
+
+#[cfg(target_arch = "arm")]
+use teensy4_fcb as _;
+#[cfg(target_arch = "arm")]
+use teensy4_panic as _;
+
+const LED_OFFSET: u32 = 3;
+
+pub mod rtic_support {
+ pub use crate::ral::*;
+}
+
+/// Prepare the board for the examples.
+///
+/// Call this first. Panics if something went wrong.
+pub fn prepare(timer_delay_microseconds: u32) -> Option<crate::Resources> {
+ let iomuxc = unsafe { ral::iomuxc::IOMUXC::instance() };
+ // Set the GPIO pad to a GPIO function (ALT 5)
+ ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_B0_03, 5);
+ // Increase drive strength, but leave other fields at their current value...
+ ral::modify_reg!(
+ ral::iomuxc,
+ iomuxc,
+ SW_PAD_CTL_PAD_GPIO_B0_03,
+ DSE: DSE_7_R0_7
+ );
+
+ let pit = crate::prepare_pit(timer_delay_microseconds)?;
+ let gpio2 = unsafe { ral::gpio::GPIO2::instance() };
+ Some(crate::Resources {
+ led: crate::Led::new(LED_OFFSET, &gpio2),
+ pit,
+ })
+}
diff --git a/cmds.gdb b/cmds.gdb
new file mode 100644
index 0000000..8116999
--- /dev/null
+++ b/cmds.gdb
@@ -0,0 +1,4 @@
+target remote localhost:3333
+monitor reg RTWDOG.CS
+monitor reg RTWDOG3.CS
+monitor reg IOMUXC_GPR.GPR16 IOMUXC_GPR.GPR17 IOMUXC_GPR.GPR18
diff --git a/examples/blink-blocking.rs b/examples/blink-blocking.rs
new file mode 100644
index 0000000..a3a71b9
--- /dev/null
+++ b/examples/blink-blocking.rs
@@ -0,0 +1,18 @@
+//! Slowly blink an LED while blocking on a timer.
+//!
+//! Use this as the minimum-viable runtime support. You don't
+//! need interrupts for this example.
+
+#![no_std]
+#![no_main]
+
+const PIT_PERIOD_US: u32 = 1_000_000;
+
+#[imxrt_rt::entry]
+fn main() -> ! {
+ let board::Resources { mut pit, led, .. } = board::prepare(PIT_PERIOD_US).unwrap();
+ loop {
+ led.toggle();
+ pit.blocking_delay();
+ }
+}
diff --git a/examples/blink-rtic.rs b/examples/blink-rtic.rs
new file mode 100644
index 0000000..04446e3
--- /dev/null
+++ b/examples/blink-rtic.rs
@@ -0,0 +1,42 @@
+//! Slowly blink an LED when a timer interrupt fires.
+//!
+//! This example is a little more complex, and shows that the
+//! vector table is placed and known to the processor.
+
+#![no_std]
+#![no_main]
+
+use imxrt_rt as _;
+
+/// A static that forces this binary to include a .data section.
+/// This is checked in an automated test.
+static mut DATA: u32 = 5;
+
+#[rtic::app(device = board::rtic_support, peripherals = false)]
+mod app {
+ const PIT_PERIOD_US: u32 = 1_000_000;
+
+ #[local]
+ struct Local {
+ led: board::Led,
+ pit: board::Pit,
+ }
+
+ #[shared]
+ struct Shared {}
+
+ #[init]
+ fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
+ let board::Resources { mut pit, led, .. } = board::prepare(PIT_PERIOD_US).unwrap();
+ pit.loop_with_interrupts();
+ led.set();
+ (Shared {}, Local { led, pit }, init::Monotonics())
+ }
+
+ #[task(binds = PIT, local = [led, pit])]
+ fn pit(cx: pit::Context) {
+ unsafe { crate::DATA += 1 };
+ cx.local.led.toggle();
+ cx.local.pit.clear_interrupts();
+ }
+}
diff --git a/src/host.rs b/src/host.rs
new file mode 100644
index 0000000..f0fa512
--- /dev/null
+++ b/src/host.rs
@@ -0,0 +1,822 @@
+//! Host-side configurations for the target.
+//!
+//! See [`RuntimeBuilder::build`] to understand the linker script generation
+//! steps.
+
+use std::{
+ env,
+ fmt::Display,
+ fs::{self, File},
+ io::{self, Write},
+ path::PathBuf,
+};
+
+/// Memory partitions.
+///
+/// Use with [`RuntimeBuilder`] to specify the placement of sections
+/// in the final program. Note that the `RuntimeBuilder` only does limited
+/// checks on memory placements. Generally, it's OK to place data in ITCM,
+/// and instructions in DTCM; however, this isn't recommended for optimal
+/// performance.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Memory {
+ /// Place the section in (external) flash.
+ ///
+ /// Reads and writes are translated into commands on an external
+ /// bus, like FlexSPI.
+ Flash,
+ /// Place the section in data tightly coupled memory (DTCM).
+ Dtcm,
+ /// Place the section in instruction tightly coupled memory (ITCM).
+ Itcm,
+ /// Place the section in on-chip RAM (OCRAM).
+ ///
+ /// If your chip includes dedicated OCRAM memory, the implementation
+ /// utilizes that OCRAM before utilizing any FlexRAM OCRAM banks.
+ Ocram,
+}
+
+/// The FlexSPI peripheral that interfaces your flash chip.
+///
+/// The [`RuntimeBuilder`] selects `FlexSpi1` for nearly all chip
+/// families. However, it selects `FlexSpi2` for the 1064 in order
+/// to utilize its on-board flash. You can override the selection
+/// using [`RuntimeBuilder::flexspi()`].
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FlexSpi {
+ /// Interface flash using FlexSPI 1.
+ FlexSpi1,
+ /// Interface flash using FlexSPI 2.
+ FlexSpi2,
+}
+
+impl FlexSpi {
+ fn family_default(family: Family) -> Self {
+ if Family::Imxrt1064 == family {
+ FlexSpi::FlexSpi2
+ } else {
+ FlexSpi::FlexSpi1
+ }
+ }
+ fn start_address(self, family: Family) -> Option<u32> {
+ match (self, family) {
+ // FlexSPI1, 10xx
+ (
+ FlexSpi::FlexSpi1,
+ Family::Imxrt1010
+ | Family::Imxrt1015
+ | Family::Imxrt1020
+ | Family::Imxrt1050
+ | Family::Imxrt1060
+ | Family::Imxrt1064,
+ ) => Some(0x6000_0000),
+ // FlexSPI2 not available on 10xx families
+ (
+ FlexSpi::FlexSpi2,
+ Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050,
+ ) => None,
+ // FlexSPI 2 available on 10xx families
+ (FlexSpi::FlexSpi2, Family::Imxrt1060 | Family::Imxrt1064) => Some(0x7000_0000),
+ // 11xx support
+ (FlexSpi::FlexSpi1, Family::Imxrt1170) => Some(0x3000_0000),
+ (FlexSpi::FlexSpi2, Family::Imxrt1170) => Some(0x6000_0000),
+ }
+ }
+ fn supported_for_family(self, family: Family) -> bool {
+ self.start_address(family).is_some()
+ }
+}
+
+impl Display for Memory {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::Flash => f.write_str("FLASH"),
+ Self::Itcm => f.write_str("ITCM"),
+ Self::Dtcm => f.write_str("DTCM"),
+ Self::Ocram => f.write_str("OCRAM"),
+ }
+ }
+}
+
+/// Define an alias for `name` that maps to a memory block named `placement`.
+fn region_alias(output: &mut dyn Write, name: &str, placement: Memory) -> io::Result<()> {
+ writeln!(output, "REGION_ALIAS(\"REGION_{}\", {});", name, placement)
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct FlashOpts {
+ size: usize,
+ flexspi: FlexSpi,
+}
+
+/// Builder for the i.MX RT runtime.
+///
+/// `RuntimeBuilder` let you assign sections to memory regions. It also lets
+/// you partition FlexRAM DTCM/ITCM/OCRAM. Call [`build()`](RuntimeBuilder::build) to commit the
+/// runtime configuration.
+///
+/// # Behaviors
+///
+/// The implementation tries to place the stack in the lowest-possible memory addresses.
+/// This means the stack will grow down into reserved memory below DTCM and OCRAM for most
+/// chip families. The outlier is the 1170, where the stack will grow into OCRAM backdoor for
+/// the CM4 coprocessor. Be careful here...
+///
+/// Similarly, the implementation tries to place the heap in the highest-possible memory
+/// addresses. This means the heap will grow up into reserved memory above DTCM and OCRAM
+/// for most chip families.
+///
+/// The vector table requires a 1024-byte alignment. The vector table's placement is prioritized
+/// above all other sections, except the stack. If placing the stack and vector table in the
+/// same section (which is the default behavior), consider keeping the stack size as a multiple
+/// of 1 KiB to minimize internal fragmentation.
+///
+/// # Default values
+///
+/// The example below demonstrates the default `RuntimeBuilder` memory placements,
+/// stack sizes, and heap sizes.
+///
+/// ```
+/// use imxrt_rt::{Family, RuntimeBuilder, Memory};
+///
+/// const FLASH_SIZE: usize = 16 * 1024;
+/// let family = Family::Imxrt1060;
+///
+/// let mut b = RuntimeBuilder::from_flexspi(family, FLASH_SIZE);
+/// // FlexRAM banks represent default fuse values.
+/// b.flexram_banks(family.default_flexram_banks());
+/// b.text(Memory::Itcm); // Copied from flash.
+/// b.rodata(Memory::Ocram); // Copied from flash.
+/// b.data(Memory::Ocram); // Copied from flash.
+/// b.vectors(Memory::Dtcm); // Copied from flash.
+/// b.bss(Memory::Ocram);
+/// b.uninit(Memory::Ocram);
+/// b.stack(Memory::Dtcm);
+/// b.stack_size(8 * 1024); // 8 KiB stack.
+/// b.heap(Memory::Dtcm); // Heap in DTCM...
+/// b.heap_size(0); // ...but no space given to the heap.
+///
+/// assert_eq!(b, RuntimeBuilder::from_flexspi(family, FLASH_SIZE));
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RuntimeBuilder {
+ family: Family,
+ flexram_banks: FlexRamBanks,
+ text: Memory,
+ rodata: Memory,
+ data: Memory,
+ vectors: Memory,
+ bss: Memory,
+ uninit: Memory,
+ stack: Memory,
+ stack_size: usize,
+ heap: Memory,
+ heap_size: usize,
+ flash_opts: Option<FlashOpts>,
+ linker_script_name: String,
+}
+
+const DEFAULT_LINKER_SCRIPT_NAME: &str = "imxrt-link.x";
+
+impl RuntimeBuilder {
+ /// Creates a runtime that can execute and load contents from
+ /// FlexSPI flash.
+ ///
+ /// `flash_size` is the size of your flash component, in bytes.
+ pub fn from_flexspi(family: Family, flash_size: usize) -> Self {
+ Self {
+ family,
+ flexram_banks: family.default_flexram_banks(),
+ text: Memory::Itcm,
+ rodata: Memory::Ocram,
+ data: Memory::Ocram,
+ vectors: Memory::Dtcm,
+ bss: Memory::Ocram,
+ uninit: Memory::Ocram,
+ stack: Memory::Dtcm,
+ stack_size: 8 * 1024,
+ heap: Memory::Dtcm,
+ heap_size: 0,
+ flash_opts: Some(FlashOpts {
+ size: flash_size,
+ flexspi: FlexSpi::family_default(family),
+ }),
+ linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(),
+ }
+ }
+ /// Set the FlexRAM bank allocation.
+ ///
+ /// Use this to customize the sizes of DTCM, ITCM, and OCRAM.
+ /// See the `FlexRamBanks` documentation for requirements on the
+ /// bank allocations.
+ pub fn flexram_banks(&mut self, flexram_banks: FlexRamBanks) -> &mut Self {
+ self.flexram_banks = flexram_banks;
+ self
+ }
+ /// Set the memory placement for code.
+ pub fn text(&mut self, memory: Memory) -> &mut Self {
+ self.text = memory;
+ self
+ }
+ /// Set the memory placement for read-only data.
+ pub fn rodata(&mut self, memory: Memory) -> &mut Self {
+ self.rodata = memory;
+ self
+ }
+ /// Set the memory placement for mutable data.
+ pub fn data(&mut self, memory: Memory) -> &mut Self {
+ self.data = memory;
+ self
+ }
+ /// Set the memory placement for the vector table.
+ pub fn vectors(&mut self, memory: Memory) -> &mut Self {
+ self.vectors = memory;
+ self
+ }
+ /// Set the memory placement for zero-initialized data.
+ pub fn bss(&mut self, memory: Memory) -> &mut Self {
+ self.bss = memory;
+ self
+ }
+ /// Set the memory placement for uninitialized data.
+ pub fn uninit(&mut self, memory: Memory) -> &mut Self {
+ self.uninit = memory;
+ self
+ }
+ /// Set the memory placement for stack memory.
+ pub fn stack(&mut self, memory: Memory) -> &mut Self {
+ self.stack = memory;
+ self
+ }
+ /// Set the size, in bytes, of the stack.
+ pub fn stack_size(&mut self, bytes: usize) -> &mut Self {
+ self.stack_size = bytes;
+ self
+ }
+ /// Set the memory placement for the heap.
+ ///
+ /// Note that the default heap has no size. Use [`heap_size`](Self::heap_size)
+ /// to allocate space for a heap.
+ pub fn heap(&mut self, memory: Memory) -> &mut Self {
+ self.heap = memory;
+ self
+ }
+ /// Set the size, in bytes, of the heap.
+ pub fn heap_size(&mut self, bytes: usize) -> &mut Self {
+ self.heap_size = bytes;
+ self
+ }
+ /// Set the FlexSPI peripheral that interfaces flash.
+ ///
+ /// See the [`FlexSpi`] to understand the default values.
+ /// If this builder is not configuring a flash-loaded runtime, this
+ /// call is silently ignored.
+ pub fn flexspi(&mut self, peripheral: FlexSpi) -> &mut Self {
+ if let Some(flash_opts) = &mut self.flash_opts {
+ flash_opts.flexspi = peripheral;
+ }
+ self
+ }
+
+ /// Set the name of the linker script file.
+ ///
+ /// You can use this to customize the linker script name for your users.
+ /// See the [crate-level documentation](crate#linker-script) for more
+ /// information.
+ pub fn linker_script_name(&mut self, name: &str) -> &mut Self {
+ self.linker_script_name = name.into();
+ self
+ }
+
+ /// Commit the runtime configuration.
+ ///
+ /// # Errors
+ ///
+ /// The implementation ensures that your chip can support the FlexRAM bank
+ /// allocation. An invalid allocation is signaled by an error.
+ ///
+ /// Returns an error if any of the following sections are placed in flash:
+ ///
+ /// - data
+ /// - vectors
+ /// - bss
+ /// - uninit
+ /// - stack
+ /// - heap
+ ///
+ /// The implementation may rely on the _linker_ to signal other errors.
+ /// For example, suppose a runtime configuration with no ITCM banks. If a
+ /// section is placed in ITCM, that error could be signaled here, or through
+ /// the linker. No matter the error path, the implementation ensures that there
+ /// will be an error.
+ pub fn build(&self) -> Result<(), Box<dyn std::error::Error>> {
+ self.check_configurations()?;
+
+ // Since `build` is called from a build script, the output directory
+ // represents the path to the _user's_ crate.
+ let out_dir = PathBuf::from(env::var("OUT_DIR")?);
+ println!("cargo:rustc-link-search={}", out_dir.display());
+
+ // The main linker script expects to INCLUDE this file. This file
+ // uses region aliases to associate region names to actual memory
+ // regions (see the Memory enum).
+ let mut memory_x = File::create(out_dir.join("imxrt-memory.x"))?;
+
+ if let Some(flash_opts) = &self.flash_opts {
+ write_flash_memory_map(&mut memory_x, self.family, flash_opts, &self.flexram_banks)?;
+ } else {
+ write_ram_memory_map(&mut memory_x, self.family, &self.flexram_banks)?;
+ }
+
+ #[cfg(feature = "device")]
+ writeln!(&mut memory_x, "INCLUDE device.x")?;
+
+ // Keep these alias names in sync with the primary linker script.
+ // The main linker script uses these region aliases for placing
+ // sections. Then, the user specifies the actual placement through
+ // the builder. This saves us the step of actually generating SECTION
+ // commands.
+ region_alias(&mut memory_x, "TEXT", self.text)?;
+ region_alias(&mut memory_x, "VTABLE", self.vectors)?;
+ region_alias(&mut memory_x, "RODATA", self.rodata)?;
+ region_alias(&mut memory_x, "DATA", self.data)?;
+ region_alias(&mut memory_x, "BSS", self.bss)?;
+ region_alias(&mut memory_x, "UNINIT", self.uninit)?;
+
+ region_alias(&mut memory_x, "STACK", self.stack)?;
+ region_alias(&mut memory_x, "HEAP", self.heap)?;
+ // Used in the linker script and / or target code.
+ writeln!(&mut memory_x, "__stack_size = {:#010X};", self.stack_size)?;
+ writeln!(&mut memory_x, "__heap_size = {:#010X};", self.heap_size)?;
+
+ if self.flash_opts.is_some() {
+ // Runtime will see different VMA and LMA, and copy the sections.
+ region_alias(&mut memory_x, "LOAD_VTABLE", Memory::Flash)?;
+ region_alias(&mut memory_x, "LOAD_TEXT", Memory::Flash)?;
+ region_alias(&mut memory_x, "LOAD_RODATA", Memory::Flash)?;
+ region_alias(&mut memory_x, "LOAD_DATA", Memory::Flash)?;
+ } else {
+ // When the VMA and LMA are equal, the runtime performs no copies.
+ region_alias(&mut memory_x, "LOAD_VTABLE", self.vectors)?;
+ region_alias(&mut memory_x, "LOAD_TEXT", self.text)?;
+ region_alias(&mut memory_x, "LOAD_RODATA", self.rodata)?;
+ region_alias(&mut memory_x, "LOAD_DATA", self.data)?;
+ }
+
+ // Referenced in target code.
+ writeln!(
+ &mut memory_x,
+ "__flexram_config = {:#010X};",
+ self.flexram_banks.config()
+ )?;
+ // The target runtime looks at this value to predicate some pre-init instructions.
+ // Could be helpful for binary identification, but it's an undocumented feature.
+ writeln!(&mut memory_x, "__imxrt_family = {};", self.family.id(),)?;
+
+ // Place the primary linker script in the user's output directory. Name may be decided
+ // by the user.
+ let link_x = include_bytes!("host/imxrt-link.x");
+ fs::write(out_dir.join(&self.linker_script_name), link_x)?;
+
+ // Also place the boot header in the search path. Do this unconditionally (even if
+ // the user is booting from RAM). Name matters, since it's INCLUDEd in our linker
+ // scripts.
+ let boot_header_x = include_bytes!("host/imxrt-boot-header.x");
+ fs::write(out_dir.join("imxrt-boot-header.x"), boot_header_x)?;
+
+ Ok(())
+ }
+
+ /// Implement i.MX RT specific sanity checks.
+ ///
+ /// This might not check everything! If the linker may detect a condition, we'll
+ /// let the linker do that.
+ fn check_configurations(&self) -> Result<(), String> {
+ if self.family.flexram_bank_count() < self.flexram_banks.bank_count() {
+ return Err(format!(
+ "Chip {:?} only has {} total FlexRAM banks. Cannot allocate {:?}, a total of {} banks",
+ self.family,
+ self.family.flexram_bank_count(),
+ self.flexram_banks,
+ self.flexram_banks.bank_count()
+ ));
+ }
+ if self.flexram_banks.ocram < self.family.bootrom_ocram_banks() {
+ return Err(format!(
+ "Chip {:?} requires at least {} OCRAM banks for the bootloader ROM",
+ self.family,
+ self.family.bootrom_ocram_banks()
+ ));
+ }
+ if let Some(flash_opts) = &self.flash_opts {
+ if !flash_opts.flexspi.supported_for_family(self.family) {
+ return Err(format!(
+ "Chip {:?} does not support {:?}",
+ self.family, flash_opts.flexspi
+ ));
+ }
+ }
+
+ fn prevent_flash(name: &str, memory: Memory) -> Result<(), String> {
+ if memory == Memory::Flash {
+ Err(format!("Section '{}' cannot be placed in flash", name))
+ } else {
+ Ok(())
+ }
+ }
+ macro_rules! prevent_flash {
+ ($sec:ident) => {
+ prevent_flash(stringify!($sec), self.$sec)
+ };
+ }
+
+ prevent_flash!(data)?;
+ prevent_flash!(vectors)?;
+ prevent_flash!(bss)?;
+ prevent_flash!(uninit)?;
+ prevent_flash!(stack)?;
+ prevent_flash!(heap)?;
+
+ Ok(())
+ }
+}
+
+/// Write RAM-like memory blocks.
+///
+/// Skips a section if there's no FlexRAM block allocated. If a user references one
+/// of this skipped sections, linking fails.
+fn write_flexram_memories(
+ output: &mut dyn Write,
+ family: Family,
+ flexram_banks: &FlexRamBanks,
+) -> io::Result<()> {
+ if flexram_banks.itcm > 0 {
+ writeln!(
+ output,
+ "ITCM (RWX) : ORIGIN = 0x00000000, LENGTH = {:#X}",
+ flexram_banks.itcm * family.flexram_bank_size(),
+ )?;
+ }
+ if flexram_banks.dtcm > 0 {
+ writeln!(
+ output,
+ "DTCM (RWX) : ORIGIN = 0x20000000, LENGTH = {:#X}",
+ flexram_banks.dtcm * family.flexram_bank_size(),
+ )?;
+ }
+
+ let ocram_size =
+ flexram_banks.ocram * family.flexram_bank_size() + family.dedicated_ocram_size();
+ if ocram_size > 0 {
+ writeln!(
+ output,
+ "OCRAM (RWX) : ORIGIN = {:#X}, LENGTH = {:#X}",
+ family.ocram_start(),
+ ocram_size,
+ )?;
+ }
+ Ok(())
+}
+
+/// Generate a linker script MEMORY command that includes a FLASH block.
+///
+/// If called, the linker script includes the boot header, which is also
+/// expressed as a linker script.
+fn write_flash_memory_map(
+ output: &mut dyn Write,
+ family: Family,
+ flash_opts: &FlashOpts,
+ flexram_banks: &FlexRamBanks,
+) -> io::Result<()> {
+ writeln!(
+ output,
+ "/* Memory map for '{:?}' with custom flash length {}. */",
+ family, flash_opts.size
+ )?;
+ writeln!(output, "MEMORY {{")?;
+ writeln!(
+ output,
+ "FLASH (RX) : ORIGIN = {:#X}, LENGTH = {:#X}",
+ flash_opts
+ .flexspi
+ .start_address(family)
+ .expect("Already checked"),
+ flash_opts.size
+ )?;
+ write_flexram_memories(output, family, flexram_banks)?;
+ writeln!(output, "}}")?;
+ writeln!(output, "__fcb_offset = {:#X};", family.fcb_offset())?;
+ writeln!(output, "INCLUDE imxrt-boot-header.x")?;
+ Ok(())
+}
+
+/// Generate a linker script MEMORY command that supports RAM execution.
+///
+/// It's like [`write_flash_memory_map`], but it doesn't include the flash
+/// important tidbits.
+fn write_ram_memory_map(
+ output: &mut dyn Write,
+ family: Family,
+ flexram_banks: &FlexRamBanks,
+) -> io::Result<()> {
+ writeln!(
+ output,
+ "/* Memory map for '{:?}' that executes from RAM. */",
+ family,
+ )?;
+ writeln!(output, "MEMORY {{")?;
+ write_flexram_memories(output, family, flexram_banks)?;
+ writeln!(output, "}}")?;
+ Ok(())
+}
+
+/// i.MX RT chip family.
+///
+/// Chip families are designed by reference manuals and produce categories.
+/// Supply this to a [`RuntimeBuilder`] in order to check runtime configurations.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Family {
+ Imxrt1010,
+ Imxrt1015,
+ Imxrt1020,
+ Imxrt1050,
+ Imxrt1060,
+ Imxrt1064,
+ Imxrt1170,
+}
+
+impl Family {
+ /// Family identifier.
+ ///
+ /// These values may be stored in the image and observe by the runtime
+ /// initialzation routine. Make sure these numbers are kept in sync with
+ /// any hard-coded values.
+ const fn id(self) -> u32 {
+ match self {
+ Family::Imxrt1010 => 1010,
+ Family::Imxrt1015 => 1015,
+ Family::Imxrt1020 => 1020,
+ Family::Imxrt1050 => 1050,
+ Family::Imxrt1060 => 1060,
+ Family::Imxrt1064 => 1064,
+ Family::Imxrt1170 => 1170,
+ }
+ }
+ /// How many FlexRAM banks are available?
+ pub const fn flexram_bank_count(self) -> u32 {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1015 => 4,
+ Family::Imxrt1020 => 8,
+ Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => 16,
+ // No ECC support; treating all banks as equal.
+ Family::Imxrt1170 => 16,
+ }
+ }
+ /// How large (bytes) is each FlexRAM bank?
+ const fn flexram_bank_size(self) -> u32 {
+ 32 * 1024
+ }
+ /// How many OCRAM banks does the boot ROM need?
+ const fn bootrom_ocram_banks(self) -> u32 {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050 => 1,
+ // 9.5.1. memory maps point at OCRAM2.
+ Family::Imxrt1060 | Family::Imxrt1064 => 0,
+ // Boot ROM uses dedicated OCRAM1.
+ Family::Imxrt1170 => 0,
+ }
+ }
+ /// Where's the FlexSPI configuration bank located?
+ fn fcb_offset(self) -> usize {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1170 => 0x400,
+ _ => 0x000,
+ }
+ }
+
+ /// Where does the OCRAM region begin?
+ ///
+ /// This includes dedicated any OCRAM regions, if any exist for the chip.
+ fn ocram_start(self) -> u32 {
+ if Family::Imxrt1170 == self {
+ // 256 KiB offset from the OCRAM M4 backdoor.
+ 0x2024_0000
+ } else {
+ // Either starts the FlexRAM OCRAM banks, or the
+ // dedicated OCRAM regions (for supported devices).
+ 0x2020_0000
+ }
+ }
+
+ /// What's the size, in bytes, of the dedicated OCRAM section?
+ ///
+ /// This isn't supported by all chips.
+ const fn dedicated_ocram_size(self) -> u32 {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050 => 0,
+ Family::Imxrt1060 | Family::Imxrt1064 => 512 * 1024,
+ // - Two dedicated OCRAMs
+ // - Two dedicated OCRAM ECC regions that aren't used for ECC
+ // - One FlexRAM OCRAM ECC region that's strictly OCRAM, without ECC
+ Family::Imxrt1170 => (2 * 512 + 2 * 64 + 128) * 1024,
+ }
+ }
+
+ /// Returns the default FlexRAM bank allocations for this chip.
+ ///
+ /// The default values represent the all-zero fuse values.
+ pub fn default_flexram_banks(self) -> FlexRamBanks {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1015 => FlexRamBanks {
+ ocram: 2,
+ itcm: 1,
+ dtcm: 1,
+ },
+ Family::Imxrt1020 => FlexRamBanks {
+ ocram: 4,
+ itcm: 2,
+ dtcm: 2,
+ },
+ Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => FlexRamBanks {
+ ocram: 8,
+ itcm: 4,
+ dtcm: 4,
+ },
+ Family::Imxrt1170 => FlexRamBanks {
+ ocram: 0,
+ itcm: 8,
+ dtcm: 8,
+ },
+ }
+ }
+}
+
+/// FlexRAM bank allocations.
+///
+/// Depending on your device, you may need a non-zero number of
+/// OCRAM banks to support the boot ROM. Consult your processor's
+/// reference manual for more information.
+///
+/// You should keep the sum of all banks below or equal to the
+/// total number of banks supported by your device. Unallocated memory
+/// banks are disabled.
+///
+/// Banks are typically 32KiB large.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct FlexRamBanks {
+ /// How many banks are allocated for OCRAM?
+ ///
+ /// This may need to be non-zero to support the boot ROM.
+ /// Consult your reference manual.
+ ///
+ /// Note: these are FlexRAM OCRAM banks. Do not include any banks
+ /// that would represent dedicated OCRAM; the runtime implementation
+ /// allocates those automatically. In fact, if your chip includes
+ /// dedicated OCRAM, you may set this to zero in order to maximize
+ /// DTCM and ITCM utilization.
+ pub ocram: u32,
+ /// How many banks are allocated for ITCM?
+ pub itcm: u32,
+ /// How many banks are allocated for DTCM?
+ pub dtcm: u32,
+}
+
+impl FlexRamBanks {
+ /// Total FlexRAM banks.
+ const fn bank_count(&self) -> u32 {
+ self.ocram + self.itcm + self.dtcm
+ }
+
+ /// Produces the FlexRAM configuration.
+ fn config(&self) -> u32 {
+ assert!(
+ self.bank_count() <= 16,
+ "Something is wrong; this should have been checked earlier."
+ );
+
+ // If a FlexRAM memory type could be allocated
+ // to _all_ memory banks, these would represent
+ // the configuration masks...
+ const OCRAM: u32 = 0x5555_5555; // 0b01...
+ const DTCM: u32 = 0xAAAA_AAAA; // 0b10...
+ const ITCM: u32 = 0xFFFF_FFFF; // 0b11...
+
+ fn mask(bank_count: u32) -> u32 {
+ 1u32.checked_shl(bank_count * 2)
+ .map(|bit| bit - 1)
+ .unwrap_or(u32::MAX)
+ }
+
+ let ocram_mask = mask(self.ocram);
+ let dtcm_mask = mask(self.dtcm).checked_shl(self.ocram * 2).unwrap_or(0);
+ let itcm_mask = mask(self.itcm)
+ .checked_shl((self.ocram + self.dtcm) * 2)
+ .unwrap_or(0);
+
+ (OCRAM & ocram_mask) | (DTCM & dtcm_mask) | (ITCM & itcm_mask)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::FlexRamBanks;
+
+ #[test]
+ fn flexram_config() {
+ /// Testing table of banks and expected configuration mask.
+ #[allow(clippy::unusual_byte_groupings)] // Spacing delimits ITCM / DTCM / OCRAM banks.
+ const TABLE: &[(FlexRamBanks, u32)] = &[
+ (
+ FlexRamBanks {
+ ocram: 16,
+ dtcm: 0,
+ itcm: 0,
+ },
+ 0x55555555,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 0,
+ dtcm: 16,
+ itcm: 0,
+ },
+ 0xAAAAAAAA,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 0,
+ dtcm: 0,
+ itcm: 16,
+ },
+ 0xFFFFFFFF,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 0,
+ dtcm: 0,
+ itcm: 0,
+ },
+ 0,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 1,
+ dtcm: 1,
+ itcm: 1,
+ },
+ 0b11_10_01,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 3,
+ dtcm: 3,
+ itcm: 3,
+ },
+ 0b111111_101010_010101,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 5,
+ dtcm: 5,
+ itcm: 5,
+ },
+ 0b1111111111_1010101010_0101010101,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 1,
+ dtcm: 1,
+ itcm: 14,
+ },
+ 0b1111111111111111111111111111_10_01,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 1,
+ dtcm: 14,
+ itcm: 1,
+ },
+ 0b11_1010101010101010101010101010_01,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 14,
+ dtcm: 1,
+ itcm: 1,
+ },
+ 0b11_10_0101010101010101010101010101,
+ ),
+ ];
+
+ for (banks, expected) in TABLE {
+ let actual = banks.config();
+ assert!(
+ actual == *expected,
+ "\nActual: {actual:#034b}\nExpected: {expected:#034b}\nBanks: {banks:?}"
+ );
+ }
+ }
+}
diff --git a/src/host/imxrt-boot-header.x b/src/host/imxrt-boot-header.x
new file mode 100644
index 0000000..67355a2
--- /dev/null
+++ b/src/host/imxrt-boot-header.x
@@ -0,0 +1,73 @@
+/* This extra file is injected into imxrt-memory.x depending on the
+ * runtime configuration.
+ */
+
+/* If you're ever playing with the boot ROM copy, this is your image size.
+ *
+ * Note that it depends on the section layout! Need to represent contiguous
+ * sections starting from the boot header.
+ */
+__image_size = SIZEOF(.boot) + SIZEOF(.vector_table) + SIZEOF(.text) + SIZEOF(.rodata);
+
+/* END TODO */
+EXTERN(FLEXSPI_CONFIGURATION_BLOCK);
+
+/* # Sections */
+SECTIONS
+{
+ /* Boot header for serial NOR FlexSPI XIP.
+ *
+ * It's 'XIP' in that it starts executing instructions
+ * from flash immediately out of reset. The runtime then
+ * manually copies instructions (data, etc.), and we jump
+ * to that. After that jump, we're no longer XIP.
+ *
+ * The i.MX RT boot ROM also supports a way to copy the
+ * application image by changing the boot data configuration.
+ * Specifically, point the 'start of image' to somewhere other
+ * than the start of flash, and specify how many bytes to copy.
+ * The boot ROM copies the image, then jumps to the vector table.
+ * There's a catch: the boot ROM copies the first 8K from the
+ * start of flash too. This represents the entire boot header,
+ * including the FCB, IVT, and boot data. (NXP docs say that the
+ * initial load region is 4K; my testing shows that it's 8K, and
+ * this aligns with observations of others.) If you ever want to
+ * try this, make sure you're specifing the VMA and LMA of the
+ * boot head section to represent this 8K relocation.
+ */
+ .boot ORIGIN(FLASH):
+ {
+ . += __fcb_offset; /* Changes based on the chip */
+ KEEP(*(.fcb));
+ . = ORIGIN(FLASH) + 0x1000;
+ /* ------------------
+ * Image vector table
+ * ------------------
+ *
+ * Not to be confused with the ARM vector table. This tells the boot ROM
+ * where to find the boot data and (eventual) first vector table.
+ * The IVT needs to reside right here.
+ */
+ __ivt = .;
+ LONG(0x402000D1); /* Header, magic number */
+ LONG(__sivector_table); /* Address of the vectors table */
+ LONG(0x00000000); /* RESERVED */
+ LONG(0x00000000); /* Device Configuration Data (unused) */
+ LONG(__boot_data); /* Address to boot data */
+ LONG(__ivt); /* Self reference */
+ LONG(0x00000000); /* Command Sequence File (unused) */
+ LONG(0x00000000); /* RESERVED */
+ /* ---------
+ * Boot data
+ * ---------
+ */
+ __boot_data = .;
+ LONG(ORIGIN(FLASH)); /* Start of image */
+ LONG(__image_size); /* Length of image */
+ LONG(0x00000000); /* Plugin flag (unused) */
+ LONG(0xDEADBEEF); /* Dummy to align boot data to 16 bytes */
+ *(.Reset); /* Jam the imxrt-rt reset handler into flash. */
+ *(.__pre_init); /* Also jam the pre-init function, since we need it to run before instructions are placed. */
+ . = ORIGIN(FLASH) + 0x2000; /* Reserve the remaining 8K as a convenience for a non-XIP boot. */
+ } > FLASH
+}
diff --git a/src/host/imxrt-link.x b/src/host/imxrt-link.x
new file mode 100644
index 0000000..272ffd8
--- /dev/null
+++ b/src/host/imxrt-link.x
@@ -0,0 +1,198 @@
+/*
+ * This linker script is a fork of the default linker script provided by
+ * imxrt-rt, version 0.7.1. It's modified to support the needs of imxrt-rt.
+ */
+
+/* Provides information about the memory layout of the device */
+/* This will be provided by the build script that uses imxrt-rt. */
+INCLUDE imxrt-memory.x
+
+/* # Entry point = reset vector */
+EXTERN(__RESET_VECTOR);
+EXTERN(Reset);
+ENTRY(Reset);
+
+/* # Exception vectors */
+/* This is effectively weak aliasing at the linker level */
+/* The user can override any of these aliases by defining the corresponding symbol themselves (cf.
+ the `exception!` macro) */
+EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */
+
+EXTERN(DefaultHandler);
+
+PROVIDE(NonMaskableInt = DefaultHandler);
+EXTERN(HardFaultTrampoline);
+PROVIDE(MemoryManagement = DefaultHandler);
+PROVIDE(BusFault = DefaultHandler);
+PROVIDE(UsageFault = DefaultHandler);
+PROVIDE(SecureFault = DefaultHandler);
+PROVIDE(SVCall = DefaultHandler);
+PROVIDE(DebugMonitor = DefaultHandler);
+PROVIDE(PendSV = DefaultHandler);
+PROVIDE(SysTick = DefaultHandler);
+
+PROVIDE(DefaultHandler = DefaultHandler_);
+PROVIDE(HardFault = HardFault_);
+
+/* # Interrupt vectors */
+EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */
+
+/* # Sections */
+SECTIONS
+{
+ .stack (NOLOAD) : ALIGN(8)
+ {
+ __estack = .;
+ . += ALIGN(__stack_size, 8);
+ __sstack = .;
+ /* Symbol expected by cortex-m-rt */
+ _stack_start = __sstack;
+ } > REGION_STACK
+
+ .vector_table : ALIGN(1024)
+ {
+ __vector_table = .;
+ __svector_table = .;
+
+ /* Initial Stack Pointer (SP) value */
+ LONG(__sstack);
+
+ /* Reset vector */
+ KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */
+ __reset_vector = .;
+
+ /* Exceptions */
+ KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */
+ __eexceptions = .;
+
+ /* Device specific interrupts */
+ KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */
+ __evector_table = .;
+ } > REGION_VTABLE AT> REGION_LOAD_VTABLE
+ __sivector_table = LOADADDR(.vector_table);
+
+ .text :
+ {
+ __stext = .;
+ *(.text .text.*);
+ /* Included in .text if not otherwise included in the boot header. */
+ *(.Reset);
+ *(.__pre_init);
+ /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`,
+ so must be placed close to it. */
+ *(.HardFaultTrampoline);
+ *(.HardFault.*);
+ . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */
+ __etext = .;
+ } > REGION_TEXT AT> REGION_LOAD_TEXT
+ __sitext = LOADADDR(.text);
+
+ .rodata : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __srodata = .;
+ *(.rodata .rodata.*);
+
+ /* 4-byte align the end (VMA) of this section.
+ This is required by LLD to ensure the LMA of the following .data
+ section will have the correct alignment. */
+ . = ALIGN(4);
+ __erodata = .;
+ } > REGION_RODATA AT> REGION_LOAD_RODATA
+ __sirodata = LOADADDR(.rodata);
+
+ .data : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __sdata = .;
+ *(.data .data.*);
+ . = ALIGN(4); /* 4-byte align the end (VMA) of this section */
+ __edata = .;
+ } > REGION_DATA AT> REGION_LOAD_DATA
+ __sidata = LOADADDR(.data);
+
+ .bss (NOLOAD) : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __sbss = .;
+ *(.bss .bss.*);
+ *(COMMON); /* Uninitialized C statics */
+ . = ALIGN(4); /* 4-byte align the end (VMA) of this section */
+ __ebss = .;
+ } > REGION_BSS
+
+ .uninit (NOLOAD) : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __suninit = .;
+ *(.uninit .uninit.*);
+ . = ALIGN(4);
+ __euninit = .;
+ } > REGION_UNINIT
+
+ .heap (NOLOAD) : ALIGN(4)
+ {
+ __sheap = .;
+ . += ALIGN(__heap_size, 4);
+ __eheap = .;
+ } > REGION_HEAP
+
+ /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in
+ the input files and raise an error if relocatable code is found */
+ .got (NOLOAD) :
+ {
+ KEEP(*(.got .got.*));
+ }
+
+ /DISCARD/ :
+ {
+ /* Unused exception related info that only wastes space */
+ *(.ARM.exidx);
+ *(.ARM.exidx.*);
+ *(.ARM.extab.*);
+ }
+}
+
+/* Do not exceed this mark in the error messages below | */
+/* # Alignment checks */
+
+ASSERT(__sstack % 8 == 0 && __estack % 8 == 0, "
+BUG(imxrt-rt): .stack is not 8-byte aligned");
+
+ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, "
+BUG(imxrt-rt): .data is not 4-byte aligned");
+
+ASSERT(__sidata % 4 == 0, "
+BUG(imxrt-rt): the LMA of .data is not 4-byte aligned");
+
+ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, "
+BUG(imxrt-rt): .bss is not 4-byte aligned");
+
+ASSERT(__sheap % 4 == 0, "
+BUG(imxrt-rt): start of .heap is not 4-byte aligned");
+
+/* # Position checks */
+
+/* ## .vector_table */
+ASSERT(__reset_vector == ADDR(.vector_table) + 0x8, "
+BUG(imxrt-rt): the reset vector is missing");
+
+ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, "
+BUG(imxrt-rt): the exception vectors are missing");
+
+ASSERT(SIZEOF(.vector_table) > 0x40, "
+ERROR(imxrt-rt): The interrupt vectors are missing.
+Possible solutions, from most likely to less likely:
+- Link to imxrt-ral, or another compatible device crate
+- Check that you actually use the device/hal/bsp crate in your code
+- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency
+may be enabling it)
+- Supply the interrupt handlers yourself. Check the documentation for details.");
+
+/* # Other checks */
+ASSERT(SIZEOF(.got) == 0, "
+ERROR(imxrt-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/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..76ba2fc
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,167 @@
+//! Runtime and startup support for i.MX RT processors.
+//!
+//! This crate builds on `cortex-m-rt` and adds support for i.MX RT processors.
+//! Using this runtime crate, you can specify FlexRAM sizes and section allocations,
+//! then use it to boot your i.MX RT processor.
+//!
+//! The crate achieves this with
+//!
+//! - a build-time API to define the memory map.
+//! - a runtime library to configure the embedded processor.
+//!
+//! Both APIs are exposed from the same package. The interface changes depending on the
+//! build environment.
+//!
+//! # Getting started
+//!
+//! Make sure you're familiar with [`cortex-m-rt`][cmrt] features. This crate re-exports
+//! the `cortex-m-rt` interface. Use this interface to implement your program's entrypoint,
+//! register exceptions, and interrupts. You should be familiar with specifing a linker
+//! script for your embedded project.
+//!
+//! [cmrt]: https://docs.rs/cortex-m-rt/0.7.1/cortex_m_rt/
+//!
+//! # Dependencies
+//!
+//! In your embedded target, depend on `imxrt-rt` in both of
+//!
+//! - the `[dependencies]` section of your Cargo.toml
+//! - the `[build-dependencies]` section of your Cargo.toml
+//!
+//! Use the same crate version in both locations. If you enable features, you must enable
+//! features in both locations. See the features section for more information.
+//!
+//! ```text
+//! [dependencies.imxrt-rt]
+//! version = # $VERSION
+//!
+//! [build-dependencies.imxrt-rt]
+//! version = # Same as $VERSION
+//! ```
+//!
+//! # Linker script
+//!
+//! **Link against `imxrt-link.x`**, which is automatically made available on the linker search path.
+//! Do not link against `link.x` from `cortex-m-rt`.
+//!
+//! You may change the name of the linker script by using the `RuntimeBuilder`.
+//!
+//! # Host configuration
+//!
+//! In your project, create a `build.rs` script that configures the runtime. The simplest `build.rs`
+//! looks like this:
+//!
+//! ```no_run
+//! use imxrt_rt::{Family, RuntimeBuilder};
+//!
+//! /// CHANGE ME depending on your board's flash size.
+//! const FLASH_SIZE: usize = 16 * 1024 * 1024; // 16 MiB.
+//! /// CHANGE ME depending on your board's chip.
+//! const FAMILY: Family = Family::Imxrt1060;
+//!
+//! fn main() {
+//! RuntimeBuilder::from_flexspi(FAMILY, FLASH_SIZE)
+//! .build()
+//! .unwrap();
+//! }
+//! ```
+//!
+//! This script works for any i.MX RT 1060-based system that has 16 MiB of external flash.
+//! Change the flash size and chip family based on your hardware. It uses the default configuration,
+//! which tries to give a reasonable memory layout for all processors.
+//! To understand the default configuration, see the [`RuntimeBuilder`] documentation.
+//!
+//! A more advanced runtime configuration looks like this:
+//!
+//! ```no_run
+//! # use imxrt_rt::{Family, RuntimeBuilder};
+//! use imxrt_rt::{FlexRamBanks, Memory};
+//! # const FLASH_SIZE: usize = 16 * 1024 * 1024; // 16 MiB.
+//! # const FAMILY: Family = Family::Imxrt1060;
+//!
+//! fn main() {
+//! RuntimeBuilder::from_flexspi(FAMILY, FLASH_SIZE)
+//! .flexram_banks(FlexRamBanks {
+//! ocram: 0,
+//! dtcm: FAMILY.flexram_bank_count() / 2 + 2,
+//! itcm: FAMILY.flexram_bank_count() / 2 - 2,
+//! })
+//! .text(Memory::Itcm)
+//! .vectors(Memory::Itcm)
+//! .rodata(Memory::Dtcm)
+//! .data(Memory::Dtcm)
+//! .bss(Memory::Dtcm)
+//! .uninit(Memory::Dtcm)
+//! .stack(Memory::Dtcm)
+//! .stack_size(4 * 1024)
+//! .heap(Memory::Dtcm)
+//! .heap_size(512)
+//! .build()
+//! .unwrap();
+//! }
+//! ```
+//!
+//! This configuration maximizes the TCM allocation by removing OCRAM blocks. It takes two
+//! banks from ITCM, and gives them to DTCM. It ensures that all sections are allocated to
+//! DTCM instead of OCRAM. It reduces the stack size, and reserves space for a small heap.
+//!
+//! No matter the configuration, the runtime ensures that all contents are copied from flash
+//! into their respective locations before `main()` is called.
+//!
+//! # Target integration
+//!
+//! If your runtime uses flash, link against a FlexSPI configuration block (FCB) crate. The
+//! crate is expected to export a `static FLEXSPI_CONFIGURATION_BLOCK` that describes how the
+//! FlexSPI peripheral interacts with your external flash chip. If an FCB crate doesn't exist
+//! for your hardware, you can use the [`imxrt-boot-gen` crate](https://docs.rs/imxrt-boot-gen/0.2.0/imxrt_boot_gen/)
+//! to define one. See the [`teensy4-fcb` crate](https://docs.rs/teensy4-fcb/0.3.0/teensy4_fcb/)
+//! for an example of an FCB crate that is compatible with this runtime.
+//!
+//! Finally, use `imxrt-rt` in your firmware just as you would use `cortex-m-rt`. See the [`cortex-m-rt`
+//! documentation][cmrt] for examples.
+//!
+//! # Feature flags
+//!
+//! `imxrt-rt` supports the features available in `cortex-m-rt` version 0.7.1. If you enable a feature,
+//! you must enable it in both the `[dependencies]` and `[build-dependencies]` section of your package
+//! manifest. For example, if the `cortex-m-rt` `"device"` feature were needed, then enable this crate's
+//! `"device"` feature in both places.
+//!
+//! ```text
+//! [dependencies.imxrt-rt]
+//! version = # $VERSION
+//! features = ["device"] # Add the feature here...
+//!
+//! [build-dependencies.imxrt-rt]
+//! version = # Same as $VERSION
+//! features = ["device"] # ... and here
+//! ```
+//!
+//! # Limitations
+//!
+//! The crate considers the assignment of FlexRAM memory banks to ITCM/DTCM/OCRAM
+//! an implementation detail. Additionally, the implementation does not care
+//! about the assignment of memory bank power domains. This seems to matter most on
+//! the 1050, which has the widest spread of bank-to-power domain assignment
+//! (according to AN12077).
+//!
+//! There is no support for ECC on 1170. The runtime assumes that OCRAM and TCM ECC
+//! is disabled, and that the corresponding memory banks can be used for OCRAM.
+//!
+//! The runtime installs a `cortex-m-rt` `pre_init` function to configure the runtime.
+//! You cannot also define a `pre_init` function, and this crate does not support any
+//! other mechanism for running code before `main()`.
+//!
+//! The implementation assumes all flash is FlexSPI.
+
+#![cfg_attr(all(target_arch = "arm", target_os = "none"), no_std)]
+
+cfg_if::cfg_if! {
+ if #[cfg(all(target_arch = "arm", target_os = "none"))] {
+ mod target;
+ pub use target::*;
+ } else {
+ mod host;
+ pub use host::*;
+ }
+}
diff --git a/src/target.rs b/src/target.rs
new file mode 100644
index 0000000..163fd8e
--- /dev/null
+++ b/src/target.rs
@@ -0,0 +1,147 @@
+//! i.MX RT target support.
+//!
+//! Defines a `cortex-m-rt` pre-init function that disables watchdogs and initializes TCM.
+//! It then copies instructions, read-only data, and the vector table to their intended location.
+//! This only happens if LMAs and VMAs differ.
+//!
+//! There's a few behaviors worth mentioning:
+//!
+//! - The implementation never clears the INIT_xTCM_EN bits in GPR16 if the xTCM is unused.
+//! The first reason is because we can't do this on the 1170 chips; the bits are reserved and
+//! should always be set (guessing its for the CM4, which always uses TCM). The second reason
+//! is that it doesn't seem necessary; we'll let xTCM initialize out of non-POR reset. From what
+//! I could gather, this would be the case if we set fuse values to specify an all-OCRAM config,
+//! and nothing says we need to flip these bits if the _fuses_ don't allocate xTCM. (Maybe this
+//! automagically happens? Not sure.)
+//! - We're not changing CM7_xTCMSZ to reflect the xTCM sizes. Again, the setting isn't available
+//! on the 1170 chips. It's also OK to keep the POR value, since it represents the maximum-possible
+//! TCM size. This means that users have finer control over xTCM memory sizes, but invalid xTCM accesses
+//! won't cause a bus fault. See 3.1.3.2. in AN12077 for more discussion.
+
+use core::{arch::global_asm, ffi::c_void};
+
+pub use cortex_m_rt::*;
+
+global_asm! {r#"
+.cfi_sections .debug_frame
+.section .__pre_init,"ax"
+.global __pre_init
+.type __pre_init,%function
+.thumb_func
+.cfi_startproc
+
+__pre_init:
+ ldr r0, =__imxrt_family @ Need to know which chip family we're initializing.
+ ldr r1, =1170
+ cmp r0, r1 @ Is this an 1170?
+
+ # Disable RTWODOG3.
+ ite eq
+ ldreq r2, =0x40038000 @ RTWDOG base address for 11xx chips...
+ ldrne r2, =0x400BC000 @ RTWDOG base address for 10xx chips...
+ ldr r3, =0xD928C520 @ RTWDOG magic number
+ str r3, [r2, #4] @ RTWDOG[CNT] = 0xD928C520.
+ ldr r3, [r2] @ r3 = RTWDOG[CS]
+ bic r3, r3, #1<<7 @ r3 = r3 & !(1 << 7), clears enable.
+ str r3, [r2] @ RTWDOG[CS] = r3
+
+ # Prepare FlexRAM regions.
+ ldr r0, =0x400AC000 @ IMXRT_IOMUXC_GPR base address for 10xx chips, overwritten if actually 11xx...
+ ldr r1, =__flexram_config @ Value for GPR17 (and GPR18 for 11xx)
+ itttt eq @ Need a few extra operations to handle 1170 split banks.
+ ldreq r0, =0x400E4000 @ IMXRT_IOMUXC_GPR base address for 11xx chips, overwrite 10xx address...
+ lsreq r2, r1, #16 @ r2 = ((unsigned)r1 >> 16)
+ streq r2, [r0, #72] @ *(IMXRT_IOMUXC_GPR + 18) = r2
+ ubfxeq r1, r1, #0, #16 @ r1 = ((unsigned)r1 >> 0) & 0xFFFF, overwrite r1 with lower halfword.
+ str r1, [r0, #68] @ *(IMXRT_IOMUXC_GPR + 17) = r1
+ ldr r1, [r0, #64] @ r1 = *(IMXRT_IOMUXC_GPR + 16)
+ orr r1, r1, #1<<2 @ r1 |= 1 << 2
+ str r1, [r0, #64] @ *(IMXRT_IOMUXC_GPR + 16) = r1
+
+ # Set the stack pointer.
+ #
+ # This is particularly important for the 11xx. Its boot ROM
+ # doesn't take this step before it calls into our reset
+ # handler. The 10xx boot ROM handles this differently.
+ # Also noted in
+ # https://community.nxp.com/t5/i-MX-RT/RT1176-ROM-code-does-not-set-stack-pointer-correctly/td-p/1388830
+ #
+ # If this feature is published in a future cortex-m-rt version,
+ # we could remove this. See below for VTOR, too.
+ #
+ # Shouldn't matter where we perform this within this function.
+ # We're assuming that the caller isn't using the stack.
+ ldr r0, =__sstack
+ msr msp, r0
+
+ # Conditionally copy text.
+ ldr r0, =__stext
+ ldr r2, =__sitext
+ cmp r2, r0
+ beq 42f
+
+ ldr r1, =__etext
+ 43:
+ cmp r1, r0
+ beq 42f
+ ldm r2!, {{r3}}
+ stm r0!, {{r3}}
+ b 43b
+ 42:
+
+ # Conditionally copy the vector table.
+ ldr r0, =__svector_table
+ ldr r2, =__sivector_table
+ cmp r2, r0
+ beq 52f
+
+ ldr r1, =__evector_table
+ 53:
+ cmp r1, r0
+ beq 52f
+ ldm r2!, {{r3}}
+ stm r0!, {{r3}}
+ b 53b
+ 52:
+
+ # Set VTOR. If this feature is published in a future cortex-m-rt version,
+ # we could remove this.
+ ldr r0, =0xE000ED08
+ ldr r1, =__svector_table
+ str r1, [r0]
+ dsb
+ isb
+
+ # Conditionally copy read-only data.
+ ldr r0, =__srodata
+ ldr r2, =__sirodata
+ cmp r2, r0
+ beq 62f
+
+ ldr r1, =__erodata
+ 63:
+ cmp r1, r0
+ beq 62f
+ ldm r2!, {{r3}}
+ stm r0!, {{r3}}
+ b 63b
+ 62:
+
+ # All done; back to the reset handler.
+ bx lr
+
+.cfi_endproc
+.size __pre_init, . - __pre_init
+"#
+}
+
+/// Returns a pointer to the end of the heap.
+///
+/// The returned pointer is guaranteed to be 4-byte aligned.
+#[inline]
+pub fn heap_end() -> *mut u32 {
+ extern "C" {
+ static mut __eheap: c_void;
+ }
+ unsafe { core::ptr::addr_of_mut!(__eheap) as _ }
+}
diff --git a/tests/inspect_elf.rs b/tests/inspect_elf.rs
new file mode 100644
index 0000000..c45260d
--- /dev/null
+++ b/tests/inspect_elf.rs
@@ -0,0 +1,445 @@
+//! Automatically inspect the programs generated by the examples.
+//!
+//! Do not refer to this as a specification for the runtime. These values
+//! are subject to change.
+
+#![allow(clippy::unusual_byte_groupings)] // Spacing delimits ITCM / DTCM / OCRAM banks.
+
+use goblin::elf::Elf;
+use std::{fs, path::PathBuf, process::Command};
+
+type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
+
+/// Build an example, returning a path to the ELF.
+fn cargo_build(board: &str) -> Result<PathBuf> {
+ Command::new("cargo")
+ .arg("build")
+ .arg("--example=blink-rtic")
+ .arg(format!("--features=board/{},board/rtic", board))
+ .arg("--target=thumbv7em-none-eabihf")
+ .arg(format!("--target-dir=target/{}", board))
+ .arg("--quiet")
+ .spawn()?
+ .wait()?;
+
+ let path = PathBuf::from(format!(
+ "target/{}/thumbv7em-none-eabihf/debug/examples/blink-rtic",
+ board
+ ));
+ Ok(path)
+}
+
+struct ImxrtBinary<'a> {
+ elf: &'a Elf<'a>,
+}
+
+impl<'a> ImxrtBinary<'a> {
+ fn new(elf: &'a Elf<'a>) -> Self {
+ Self { elf }
+ }
+
+ fn symbol(&self, symbol_name: &str) -> Option<goblin::elf::Sym> {
+ self.elf
+ .syms
+ .iter()
+ .flat_map(|sym| self.elf.strtab.get_at(sym.st_name).map(|name| (sym, name)))
+ .find(|(_, name)| symbol_name == *name)
+ .map(|(sym, _)| sym)
+ }
+
+ fn fcb(&self) -> Result<Fcb> {
+ self.symbol("FLEXSPI_CONFIGURATION_BLOCK")
+ .map(|sym| Fcb {
+ address: sym.st_value,
+ size: sym.st_size,
+ })
+ .ok_or_else(|| {
+ String::from("Could not find FLEXSPI_CONFIGURATION_BLOCK in program").into()
+ })
+ }
+
+ fn flexram_config(&self) -> Result<u64> {
+ self.symbol("__flexram_config")
+ .map(|sym| sym.st_value)
+ .ok_or_else(|| String::from("Could not find FlexRAM configuration in program").into())
+ }
+
+ fn section(&self, section_name: &str) -> Result<Section> {
+ self.elf
+ .section_headers
+ .iter()
+ .flat_map(|sec| {
+ self.elf
+ .shdr_strtab
+ .get_at(sec.sh_name)
+ .map(|name| (sec, name))
+ })
+ .find(|(_, name)| section_name == *name)
+ .map(|(sec, _)| Section {
+ address: sec.sh_addr,
+ size: sec.sh_size,
+ })
+ .ok_or_else(|| format!("Could not find {section_name} in program").into())
+ }
+
+ fn section_lma(&self, section: &Section) -> u64 {
+ self.elf
+ .program_headers
+ .iter()
+ .filter(|phdr| goblin::elf::program_header::PT_LOAD == phdr.p_type)
+ .find(|phdr| {
+ phdr.p_vaddr <= section.address && (phdr.p_vaddr + phdr.p_memsz) > section.address
+ })
+ .map(|phdr| section.address - phdr.p_vaddr + phdr.p_paddr)
+ .unwrap_or(section.address) // VMA == LMA
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+struct Fcb {
+ address: u64,
+ size: u64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+struct Section {
+ address: u64,
+ size: u64,
+}
+
+const DTCM: u64 = 0x2000_0000;
+const ITCM: u64 = 0x0000_0000;
+
+const fn aligned(value: u64, alignment: u64) -> u64 {
+ (value + (alignment - 1)) & !(alignment - 1)
+}
+
+#[test]
+#[ignore = "building an example can take time"]
+fn imxrt1010evk() {
+ let path = cargo_build("imxrt1010evk").expect("Unable to build example");
+ let contents = fs::read(path).expect("Could not read ELF file");
+ let elf = Elf::parse(&contents).expect("Could not parse ELF");
+
+ let binary = ImxrtBinary::new(&elf);
+ assert_eq!(
+ Fcb {
+ address: 0x6000_0400,
+ size: 512
+ },
+ binary.fcb().unwrap()
+ );
+ assert_eq!(binary.flexram_config().unwrap(), 0b11_10_0101);
+
+ let stack = binary.section(".stack").unwrap();
+ assert_eq!(
+ Section {
+ address: DTCM,
+ size: 8 * 1024
+ },
+ stack,
+ "stack not at ORIGIN(DTCM), or not 8 KiB large"
+ );
+ assert_eq!(binary.section_lma(&stack), stack.address);
+
+ let vector_table = binary.section(".vector_table").unwrap();
+ assert_eq!(
+ Section {
+ address: stack.address + stack.size,
+ size: 16 * 4 + 240 * 4
+ },
+ vector_table,
+ "vector table not at expected VMA behind the stack"
+ );
+ assert!(
+ vector_table.address % 1024 == 0,
+ "vector table is not 1024-byte aligned"
+ );
+ assert_eq!(binary.section_lma(&vector_table), 0x6000_2000);
+
+ let text = binary.section(".text").unwrap();
+ assert_eq!(text.address, ITCM, "text");
+ assert_eq!(
+ binary.section_lma(&text),
+ 0x6000_2000 + vector_table.size,
+ "text VMA expected behind vector table"
+ );
+
+ let rodata = binary.section(".rodata").unwrap();
+ assert_eq!(
+ rodata.address,
+ 0x6000_2000 + vector_table.size + aligned(text.size, 16),
+ "rodata LMA & VMA expected behind text"
+ );
+ assert_eq!(rodata.address, binary.section_lma(&rodata));
+
+ let data = binary.section(".data").unwrap();
+ assert_eq!(data.address, 0x2020_0000, "data VMA in OCRAM");
+ assert_eq!(
+ data.size, 4,
+ "blink-rtic expected to have a single static mut u32"
+ );
+ assert_eq!(
+ binary.section_lma(&data),
+ rodata.address + aligned(rodata.size, 4),
+ "data LMA starts behind rodata"
+ );
+
+ let bss = binary.section(".bss").unwrap();
+ assert_eq!(
+ bss.address,
+ data.address + aligned(data.size, 4),
+ "bss in OCRAM behind data"
+ );
+ assert_eq!(binary.section_lma(&bss), bss.address, "bss is NOLOAD");
+
+ let uninit = binary.section(".uninit").unwrap();
+ assert_eq!(
+ uninit.address,
+ bss.address + aligned(bss.size, 4),
+ "uninit in OCRAM behind bss"
+ );
+ assert_eq!(
+ binary.section_lma(&uninit),
+ uninit.address,
+ "uninit is NOLOAD"
+ );
+
+ let heap = binary.section(".heap").unwrap();
+ assert_eq!(
+ Section {
+ address: vector_table.address + vector_table.size,
+ size: 1024
+ },
+ heap,
+ "1 KiB heap in DTCM behind vector table"
+ );
+ assert_eq!(heap.size, 1024);
+ assert_eq!(binary.section_lma(&heap), heap.address, "Heap is NOLOAD");
+}
+
+#[test]
+#[ignore = "building an example can take time"]
+fn teensy4() {
+ let path = cargo_build("teensy4").expect("Unable to build example");
+ let contents = fs::read(path).expect("Could not read ELF file");
+ let elf = Elf::parse(&contents).expect("Could not parse ELF");
+
+ let binary = ImxrtBinary::new(&elf);
+ assert_eq!(
+ Fcb {
+ address: 0x6000_0000,
+ size: 512
+ },
+ binary.fcb().unwrap()
+ );
+ assert_eq!(
+ binary.flexram_config().unwrap(),
+ 0b11111111_101010101010101010101010
+ );
+
+ let stack = binary.section(".stack").unwrap();
+ assert_eq!(
+ Section {
+ address: DTCM,
+ size: 8 * 1024
+ },
+ stack,
+ "stack not at ORIGIN(DTCM), or not 8 KiB large"
+ );
+ assert_eq!(binary.section_lma(&stack), stack.address);
+
+ let vector_table = binary.section(".vector_table").unwrap();
+ assert_eq!(
+ Section {
+ address: stack.address + stack.size,
+ size: 16 * 4 + 240 * 4
+ },
+ vector_table,
+ "vector table not at expected VMA behind the stack"
+ );
+ assert!(
+ vector_table.address % 1024 == 0,
+ "vector table is not 1024-byte aligned"
+ );
+ assert_eq!(binary.section_lma(&vector_table), 0x6000_2000);
+
+ let text = binary.section(".text").unwrap();
+ assert_eq!(
+ text.address,
+ binary.section_lma(&vector_table) + vector_table.size,
+ "text"
+ );
+ assert_eq!(
+ binary.section_lma(&text),
+ 0x6000_2000 + vector_table.size,
+ "text VMA expected behind vector table"
+ );
+
+ let rodata = binary.section(".rodata").unwrap();
+ assert_eq!(
+ rodata.address,
+ vector_table.address + vector_table.size,
+ "rodata LMA & VMA expected behind text"
+ );
+ assert_eq!(
+ binary.section_lma(&rodata),
+ binary.section_lma(&text) + aligned(text.size, 16)
+ );
+
+ let data = binary.section(".data").unwrap();
+ assert_eq!(
+ data.address,
+ rodata.address + rodata.size,
+ "data VMA in DTCM behind rodata"
+ );
+ assert_eq!(
+ data.size, 4,
+ "blink-rtic expected to have a single static mut u32"
+ );
+ assert_eq!(
+ binary.section_lma(&data),
+ binary.section_lma(&rodata) + aligned(rodata.size, 4),
+ "data LMA starts behind rodata"
+ );
+
+ let bss = binary.section(".bss").unwrap();
+ assert_eq!(
+ bss.address,
+ data.address + aligned(data.size, 4),
+ "bss in DTCM behind data"
+ );
+ assert_eq!(binary.section_lma(&bss), bss.address, "bss is NOLOAD");
+
+ let uninit = binary.section(".uninit").unwrap();
+ assert_eq!(
+ uninit.address,
+ bss.address + aligned(bss.size, 4),
+ "uninit in DTCM behind bss"
+ );
+ assert_eq!(
+ binary.section_lma(&uninit),
+ uninit.address,
+ "uninit is NOLOAD"
+ );
+
+ let heap = binary.section(".heap").unwrap();
+ assert_eq!(
+ Section {
+ address: uninit.address + aligned(uninit.size, 4),
+ size: 1024
+ },
+ heap,
+ "1 KiB heap in DTCM behind uninit"
+ );
+ assert_eq!(binary.section_lma(&heap), heap.address, "Heap is NOLOAD");
+}
+
+#[test]
+#[ignore = "building an example can take time"]
+fn imxrt1170evk_cm7() {
+ let path = cargo_build("imxrt1170evk-cm7").expect("Unable to build example");
+ let contents = fs::read(path).expect("Could not read ELF file");
+ let elf = Elf::parse(&contents).expect("Could not parse ELF");
+
+ let binary = ImxrtBinary::new(&elf);
+ assert_eq!(
+ Fcb {
+ address: 0x3000_0400,
+ size: 512
+ },
+ binary.fcb().unwrap()
+ );
+ assert_eq!(
+ binary.flexram_config().unwrap(),
+ 0b1111111111111111_1010101010101010
+ );
+
+ let stack = binary.section(".stack").unwrap();
+ assert_eq!(
+ Section {
+ address: DTCM,
+ size: 8 * 1024
+ },
+ stack,
+ "stack not at ORIGIN(DTCM), or not 8 KiB large"
+ );
+ assert_eq!(binary.section_lma(&stack), stack.address);
+
+ let vector_table = binary.section(".vector_table").unwrap();
+ assert_eq!(
+ Section {
+ address: stack.address + stack.size,
+ size: 16 * 4 + 240 * 4
+ },
+ vector_table,
+ "vector table not at expected VMA behind the stack"
+ );
+ assert!(
+ vector_table.address % 1024 == 0,
+ "vector table is not 1024-byte aligned"
+ );
+ assert_eq!(binary.section_lma(&vector_table), 0x3000_2000);
+
+ let text = binary.section(".text").unwrap();
+ assert_eq!(text.address, ITCM, "text");
+ assert_eq!(
+ binary.section_lma(&text),
+ 0x3000_2000 + vector_table.size,
+ "text VMA expected behind vector table"
+ );
+
+ let rodata = binary.section(".rodata").unwrap();
+ assert_eq!(
+ rodata.address,
+ vector_table.address + vector_table.size,
+ "rodata moved to DTCM behind vector table"
+ );
+ assert_eq!(
+ binary.section_lma(&rodata),
+ 0x3000_2000 + vector_table.size + aligned(text.size, 16),
+ );
+
+ let data = binary.section(".data").unwrap();
+ assert_eq!(data.address, 0x2024_0000, "data VMA in OCRAM");
+ assert_eq!(
+ data.size, 4,
+ "blink-rtic expected to have a single static mut u32"
+ );
+ assert_eq!(
+ binary.section_lma(&data),
+ binary.section_lma(&rodata) + aligned(rodata.size, 4),
+ "data LMA starts behind rodata"
+ );
+
+ let bss = binary.section(".bss").unwrap();
+ assert_eq!(
+ bss.address,
+ data.address + aligned(data.size, 4),
+ "bss in OCRAM behind data"
+ );
+ assert_eq!(binary.section_lma(&bss), bss.address, "bss is NOLOAD");
+
+ let uninit = binary.section(".uninit").unwrap();
+ assert_eq!(
+ uninit.address,
+ bss.address + aligned(bss.size, 4),
+ "uninit in OCRAM behind bss"
+ );
+ assert_eq!(
+ binary.section_lma(&uninit),
+ uninit.address,
+ "uninit is NOLOAD"
+ );
+
+ let heap = binary.section(".heap").unwrap();
+ assert_eq!(
+ Section {
+ address: rodata.address + aligned(rodata.size, 4),
+ size: 0,
+ },
+ heap,
+ "0 byte heap in DTCM behind rodata table"
+ );
+ assert_eq!(binary.section_lma(&heap), heap.address, "Heap is NOLOAD");
+}