diff options
| author | Ian McIntyre <ianpmcintyre@gmail.com> | 2022-08-02 06:21:12 -0400 |
|---|---|---|
| committer | Ian McIntyre <ianpmcintyre@gmail.com> | 2022-12-01 20:21:05 -0500 |
| commit | c7a9b9f3d4b9e71303c7b988d2bd916c2e4df9bc (patch) | |
| tree | 6d41ea7e433cac328fa165d45d1bc0cd71a1bf8f | |
First commit
| -rw-r--r-- | .cargo/config.toml | 4 | ||||
| -rw-r--r-- | .github/workflows/rust.yaml | 92 | ||||
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | Cargo.toml | 59 | ||||
| -rw-r--r-- | LICENSE-APACHE | 201 | ||||
| -rw-r--r-- | LICENSE-MIT | 21 | ||||
| -rw-r--r-- | README.md | 53 | ||||
| -rw-r--r-- | board/Cargo.toml | 47 | ||||
| -rw-r--r-- | board/README.md | 122 | ||||
| -rw-r--r-- | board/build.rs | 53 | ||||
| -rw-r--r-- | board/src/imxrt1010evk.rs | 36 | ||||
| -rw-r--r-- | board/src/imxrt1170evk_cm7.rs | 38 | ||||
| -rw-r--r-- | board/src/lib.rs | 107 | ||||
| -rw-r--r-- | board/src/shared/imxrt10xx.rs | 35 | ||||
| -rw-r--r-- | board/src/shared/imxrt11xx.rs | 52 | ||||
| -rw-r--r-- | board/src/teensy4.rs | 37 | ||||
| -rw-r--r-- | cmds.gdb | 4 | ||||
| -rw-r--r-- | examples/blink-blocking.rs | 18 | ||||
| -rw-r--r-- | examples/blink-rtic.rs | 42 | ||||
| -rw-r--r-- | src/host.rs | 822 | ||||
| -rw-r--r-- | src/host/imxrt-boot-header.x | 73 | ||||
| -rw-r--r-- | src/host/imxrt-link.x | 198 | ||||
| -rw-r--r-- | src/lib.rs | 167 | ||||
| -rw-r--r-- | src/target.rs | 147 | ||||
| -rw-r--r-- | tests/inspect_elf.rs | 445 |
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"); +} |
