├── .cargo └── config.toml ├── .github └── workflows │ ├── ci.yml │ └── issue_handler.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── examples ├── demo.rs └── low_level.rs └── src ├── common.rs ├── esp32.rs ├── esp32c2.rs ├── esp32c3.rs ├── esp32c6.rs ├── esp32h2.rs ├── esp32s2.rs ├── esp32s3.rs ├── lib.rs ├── ll.rs ├── nor_flash.rs ├── storage.rs └── stub.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.riscv32imc-unknown-none-elf] 2 | runner = "espflash flash --monitor" 3 | 4 | rustflags = [ 5 | "-C", "link-arg=-Tlinkall.x", 6 | ] 7 | 8 | [target.riscv32imac-unknown-none-elf] 9 | runner = "espflash flash --monitor" 10 | 11 | rustflags = [ 12 | "-C", "link-arg=-Tlinkall.x", 13 | ] 14 | 15 | [target.xtensa-esp32-none-elf] 16 | runner = "espflash flash --monitor" 17 | 18 | rustflags = [ 19 | "-C", "link-arg=-Tlinkall.x", 20 | ] 21 | 22 | [target.xtensa-esp32s2-none-elf] 23 | runner = "espflash flash --monitor" 24 | 25 | rustflags = [ 26 | "-C", "link-arg=-Tlinkall.x", 27 | 28 | # enable the atomic codegen option for Xtensa 29 | "-C", "target-feature=+s32c1i", 30 | 31 | # tell the core library have atomics even though it's not specified in the target definition 32 | "--cfg", "target_has_atomic_load_store", 33 | "--cfg", 'target_has_atomic_load_store="8"', 34 | "--cfg", 'target_has_atomic_load_store="16"', 35 | "--cfg", 'target_has_atomic_load_store="32"', 36 | "--cfg", 'target_has_atomic_load_store="ptr"', 37 | # enable cas 38 | "--cfg", "target_has_atomic", 39 | "--cfg", 'target_has_atomic="8"', 40 | "--cfg", 'target_has_atomic="16"', 41 | "--cfg", 'target_has_atomic="32"', 42 | "--cfg", 'target_has_atomic="ptr"', 43 | ] 44 | 45 | [target.xtensa-esp32s3-none-elf] 46 | runner = "espflash flash --monitor" 47 | 48 | rustflags = [ 49 | "-C", "link-arg=-Tlinkall.x", 50 | ] 51 | 52 | [build] 53 | target = "riscv32imc-unknown-none-elf" 54 | 55 | [unstable] 56 | build-std = [ "core", "alloc" ] 57 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | workflow_dispatch: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | 14 | # Cancel any currently running workflows from the same PR, branch, or 15 | # tag when a new workflow is triggered. 16 | # 17 | # https://stackoverflow.com/a/66336834 18 | concurrency: 19 | cancel-in-progress: true 20 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 21 | 22 | jobs: 23 | # -------------------------------------------------------------------------- 24 | # Check 25 | 26 | lib-check: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - uses: actions/checkout@v3 31 | - uses: dtolnay/rust-toolchain@v1 32 | with: 33 | target: riscv32imc-unknown-none-elf 34 | toolchain: nightly 35 | components: rust-src 36 | - uses: esp-rs/xtensa-toolchain@v1.5 37 | with: 38 | ldproxy: false 39 | override: false 40 | - uses: Swatinem/rust-cache@v2 41 | 42 | # Check all RISC-V targets: 43 | - name: check (esp32c3) 44 | run: cargo +nightly check --features=esp32c3 45 | - name: check (esp32c6) 46 | run: cargo +nightly check --features=esp32c6 47 | - name: check (esp32h2) 48 | run: cargo +nightly check --features=esp32h2 49 | # Check all Xtensa targets: 50 | - name: check (esp32) 51 | run: cargo +esp check --release --features=esp32 52 | - name: check (esp32s2) 53 | run: cargo +esp check --features=esp32s2 54 | - name: check (esp32s3) 55 | run: cargo +esp check --features=esp32s3 56 | 57 | esp32-examples: 58 | runs-on: ubuntu-latest 59 | 60 | steps: 61 | - uses: actions/checkout@v2 62 | - uses: esp-rs/xtensa-toolchain@v1.5 63 | with: 64 | default: true 65 | buildtargets: esp32 66 | ldproxy: false 67 | - uses: Swatinem/rust-cache@v1 68 | 69 | - name: build (demo) 70 | run: cargo +esp build --release --example demo --features "esp32" --target xtensa-esp32-none-elf 71 | - name: build (low_level) 72 | run: cargo +esp build --release --example low_level --features "esp32,low-level" --target xtensa-esp32-none-elf 73 | 74 | esp32c3-examples: 75 | runs-on: ubuntu-latest 76 | steps: 77 | - uses: actions/checkout@v2 78 | - uses: dtolnay/rust-toolchain@v1 79 | with: 80 | target: riscv32imc-unknown-none-elf 81 | toolchain: nightly 82 | components: rust-src 83 | - uses: Swatinem/rust-cache@v1 84 | 85 | - name: build (demo) 86 | run: cargo +nightly build --example demo --features "esp32c3" --target riscv32imc-unknown-none-elf 87 | - name: build (low_level) 88 | run: cargo +nightly build --example low_level --features "esp32c3,low-level" --target riscv32imc-unknown-none-elf 89 | 90 | esp32c6-examples: 91 | runs-on: ubuntu-latest 92 | steps: 93 | - uses: actions/checkout@v2 94 | - uses: dtolnay/rust-toolchain@v1 95 | with: 96 | target: riscv32imac-unknown-none-elf 97 | toolchain: nightly 98 | components: rust-src 99 | - uses: Swatinem/rust-cache@v1 100 | 101 | - name: check (lib) 102 | run: cargo +nightly check --features=esp32c6 103 | - name: build (demo) 104 | run: cargo +nightly build --example demo --features "esp32c6" --target riscv32imac-unknown-none-elf 105 | - name: build (low_level) 106 | run: cargo +nightly build --example low_level --features "esp32c6,low-level" --target riscv32imac-unknown-none-elf 107 | 108 | esp32s2-examples: 109 | runs-on: ubuntu-latest 110 | 111 | steps: 112 | - uses: actions/checkout@v2 113 | - uses: esp-rs/xtensa-toolchain@v1.5 114 | with: 115 | default: true 116 | buildtargets: esp32s2 117 | ldproxy: false 118 | - uses: Swatinem/rust-cache@v1 119 | 120 | - name: build (demo) 121 | run: cargo +esp build --example demo --features "esp32s2" --target xtensa-esp32s2-none-elf 122 | - name: build (low_level) 123 | run: cargo +esp build --example low_level --features "esp32s2,low-level" --target xtensa-esp32s2-none-elf 124 | 125 | esp32s3-examples: 126 | runs-on: ubuntu-latest 127 | 128 | steps: 129 | - uses: actions/checkout@v2 130 | - uses: esp-rs/xtensa-toolchain@v1.5 131 | with: 132 | default: true 133 | buildtargets: esp32s3 134 | ldproxy: false 135 | - uses: Swatinem/rust-cache@v1 136 | 137 | - name: build (demo) 138 | run: cargo +esp build --example demo --features "esp32s3" --target xtensa-esp32s3-none-elf 139 | - name: build (low_level) 140 | run: cargo +esp build --example low_level --features "esp32s3,low-level" --target xtensa-esp32s3-none-elf 141 | 142 | # -------------------------------------------------------------------------- 143 | # Lint 144 | 145 | clippy: 146 | runs-on: ubuntu-latest 147 | 148 | steps: 149 | - uses: actions/checkout@v3 150 | - uses: dtolnay/rust-toolchain@v1 151 | with: 152 | toolchain: nightly 153 | components: clippy 154 | - uses: esp-rs/xtensa-toolchain@v1.5 155 | with: 156 | ldproxy: false 157 | override: false 158 | - uses: Swatinem/rust-cache@v2 159 | 160 | # Run clippy on RISC-V. 161 | - name: clippy (esp32c2) 162 | run: cargo +nightly clippy --features esp32c2 -- --no-deps -D warnings 163 | - name: clippy (esp32c3) 164 | run: cargo +nightly clippy --features esp32c3 -- --no-deps -D warnings 165 | - name: clippy (esp32c6) 166 | run: cargo +nightly clippy --features esp32c6 -- --no-deps -D warnings 167 | - name: clippy (esp32h2) 168 | run: cargo +nightly clippy --features esp32h2 -- --no-deps -D warnings 169 | # Run clippy on Xtensa. 170 | - name: clippy (esp32) 171 | run: cargo +esp clippy --release --features esp32 -- --no-deps -D warnings 172 | - name: clippy (esp32s2) 173 | run: cargo +esp clippy --features esp32s2 -- --no-deps -D warnings 174 | - name: clippy (esp32s3) 175 | run: cargo +esp clippy --features esp32s3 -- --no-deps -D warnings 176 | 177 | rustfmt: 178 | runs-on: ubuntu-latest 179 | 180 | steps: 181 | - uses: actions/checkout@v3 182 | 183 | - uses: dtolnay/rust-toolchain@v1 184 | with: 185 | toolchain: nightly 186 | components: rustfmt 187 | - uses: Swatinem/rust-cache@v2 188 | 189 | # Check the formatting. 190 | - name: rustfmt 191 | run: cargo fmt --all -- --check 192 | -------------------------------------------------------------------------------- /.github/workflows/issue_handler.yml: -------------------------------------------------------------------------------- 1 | name: Add new issues to project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | add-to-project: 10 | name: Add issue to project 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/add-to-project@v0.5.0 14 | with: 15 | project-url: https://github.com/orgs/esp-rs/projects/2 16 | github-token: ${{ secrets.PAT }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /target_ra 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // "editor.formatOnSave": true, 3 | 4 | "rust-analyzer.server.extraEnv": { 5 | "CARGO_TARGET_DIR": "./target_ra" 6 | }, 7 | 8 | "rust-analyzer.cargo.features": [ 9 | "esp32","low-level" 10 | ], 11 | "rust-analyzer.cargo.target": "xtensa-esp32-none-elf", 12 | } 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esp-storage" 3 | version = "0.3.0" 4 | edition = "2021" 5 | authors = [ 6 | "The ESP-RS team", 7 | "Björn Quentin ", 8 | ] 9 | description = "Implementation of embedded-storage traits to access unencrypted ESP32 flash" 10 | repository = "https://github.com/esp-rs/esp-storage" 11 | license = "MIT OR Apache-2.0" 12 | 13 | keywords = [ 14 | "embedded-storage", 15 | "esp", 16 | "no-std", 17 | ] 18 | categories = [ 19 | "embedded", 20 | "hardware-support", 21 | "no-std", 22 | ] 23 | 24 | [dependencies] 25 | embedded-storage = "0.3.0" 26 | critical-section = { version = "1.1.1", optional = true } 27 | 28 | # specifying dev dependencies by target is less than ideal - however we cannot have feature gated dev-dependencies 29 | 30 | [target.riscv32imc-unknown-none-elf.dev-dependencies] 31 | # ESP32-C2 32 | # esp32c2-hal = "0.11.0" 33 | # esp-println = { version = "0.7.1", features = [ "esp32c2" ] } 34 | # esp-backtrace = { version = "0.9.0", features = [ "esp32c2", "panic-handler", "exception-handler", "print-uart"] } 35 | # ESP32-C3 36 | esp32c3-hal = "0.13.0" 37 | esp-println = { version = "0.7.1", features = [ "esp32c3" ] } 38 | esp-backtrace = { version = "0.9.0", features = [ "esp32c3", "panic-handler", "exception-handler", "print-uart"] } 39 | 40 | [target.riscv32imac-unknown-none-elf.dev-dependencies] 41 | # ESP32-C6 42 | esp32c6-hal = "0.6.0" 43 | esp-println = { version = "0.7.1", features = [ "esp32c6" ] } 44 | esp-backtrace = { version = "0.9.0", features = [ "esp32c6", "panic-handler", "exception-handler", "print-uart"] } 45 | # ESP32-H2 46 | # esp32h2-hal = "0.4.0" 47 | # esp-println = { version = "0.7.1", features = [ "esp32h2" ] } 48 | # esp-backtrace = { version = "0.9.0", features = [ "esp32h2", "panic-handler", "exception-handler", "print-uart"] } 49 | 50 | [target.xtensa-esp32-none-elf.dev-dependencies] 51 | esp32-hal = "0.16.0" 52 | esp-println = { version = "0.7.1", features = [ "esp32" ] } 53 | esp-backtrace = { version = "0.9.0", features = [ "esp32", "panic-handler", "exception-handler", "print-uart"] } 54 | 55 | [target.xtensa-esp32s2-none-elf.dev-dependencies] 56 | esp32s2-hal = "0.13.0" 57 | esp-println = { version = "0.7.1", features = [ "esp32s2" ] } 58 | esp-backtrace = { version = "0.9.0", features = [ "esp32s2", "panic-handler", "exception-handler", "print-uart"] } 59 | 60 | [target.xtensa-esp32s3-none-elf.dev-dependencies] 61 | esp32s3-hal = "0.13.0" 62 | esp-println = { version = "0.7.1", features = [ "esp32s3" ] } 63 | esp-backtrace = { version = "0.9.0", features = [ "esp32s3", "panic-handler", "exception-handler", "print-uart"] } 64 | 65 | [[example]] 66 | name = "low_level" 67 | required-features = ["low-level"] 68 | 69 | [features] 70 | default = ["critical-section", "storage"] 71 | critical-section = ["dep:critical-section"] 72 | # ReadStorage/Storage traits 73 | storage = [] 74 | # ReadNorFlash/NorFlash traits 75 | nor-flash = [] 76 | # Bytewise read emulation 77 | bytewise-read = [] 78 | esp32c2 = [] 79 | esp32c3 = [] 80 | esp32c6 = [] 81 | esp32h2 = [] 82 | esp32 = [] 83 | esp32s2 = [] 84 | esp32s3 = [] 85 | # Enable flash emulation to run tests 86 | emulation = [] 87 | 88 | # this feature is reserved for very specific use-cases - usually you don't want to use this! 89 | low-level = [] 90 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 esp-rs 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2022 esp-rs 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp-storage 2 | 3 | --- 4 | ## This project has moved! It can now be found in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal/tree/main/esp-storage) repository. 5 | --- 6 | 7 | This implements [`embedded-storage`](https://github.com/rust-embedded-community/embedded-storage) traits to access unencrypted ESP32 flash. 8 | 9 | ## Current support 10 | 11 | ESP32, ESP32-C2, ESP32-C3, ESP32-C6, ESP32-H2, ESP32-S2 and ESP32-S3 are supported in `esp-storage` 12 | 13 | ## Examples 14 | - ESP32: 15 | 1. Run the example: 16 | - `cargo +esp run --release --example demo --features esp32 --target xtensa-esp32-none-elf` 17 | - `cargo +esp run --release --example low_level --features "esp32,low-level" --target xtensa-esp32-none-elf` 18 | - ESP32-C2: 19 | 1. Uncomment the ESP32-C2 section, under `target.riscv32imc-unknown-none-elf.dev-dependencies` of the `Cargo-toml` file. 20 | 2. Run the example: 21 | - `cargo "+nightly" run --example demo --features esp32c2 --target riscv32imc-unknown-none-elf` 22 | - `cargo "+nightly" run --example low_level --features "esp32c2,low-level" --target riscv32imc-unknown-none-elf` 23 | - ESP32-C3: 24 | 1. Uncomment the ESP32-C3 section, under `target.riscv32imc-unknown-none-elf.dev-dependencies` of the `Cargo-toml` file. 25 | 2. Run the example: 26 | - `cargo "+nightly" run --example demo --features esp32c3 --target riscv32imc-unknown-none-elf` 27 | - `cargo "+nightly" run --example low_level --features "esp32c3,low-level" --target riscv32imc-unknown-none-elf` 28 | - ESP32-C6: 29 | 1. Uncomment the ESP32-C6 section, under `target.riscv32imac-unknown-none-elf.dev-dependencies` of the `Cargo-toml` file. 30 | 2. Run the example: 31 | - `cargo "+nightly" run --example demo --features esp32c6 --target riscv32imac-unknown-none-elf` 32 | - `cargo "+nightly" run --example low_level --features "esp32c6,low-level" --target riscv32imac-unknown-none-elf` 33 | - ESP32-H2: 34 | 1. Uncomment the ESP32-H2 section, under `target.riscv32imac-unknown-none-elf.dev-dependencies` of the `Cargo-toml` file. 35 | 2. Run the example: 36 | - `cargo "+nightly" run --example demo --features esp32h2 --target riscv32imac-unknown-none-elf` 37 | - `cargo "+nightly" run --example low_level --features "esp32h2,low-level" --target riscv32imac-unknown-none-elf` 38 | - ESP32-S2: 39 | 1. Run the example: 40 | - `cargo "+esp" run --example demo --features esp32s2 --target xtensa-esp32s2-none-elf` 41 | - `cargo +esp run --release --example low_level --features "esp32s2,low-level" --target xtensa-esp32s2-none-elf` 42 | - ESP32-S3: 43 | 1. Run the example: 44 | - `cargo "+esp" run --example demo --features esp32s3 --target xtensa-esp32s3-none-elf` 45 | - `cargo +esp run --release --example low_level --features "esp32s3,low-level" --target xtensa-esp32s3-none-elf` 46 | 47 | ## Important 48 | 49 | For ESP32 it is necessary to build with [optimization level](https://doc.rust-lang.org/cargo/reference/profiles.html#opt-level) 2 or 3. 50 | 51 | To make it work also for `debug` builds add this to your `Cargo.toml` 52 | 53 | ```toml 54 | [profile.dev.package.esp-storage] 55 | opt-level = 3 56 | ``` 57 | 58 | ## License 59 | 60 | Licensed under either of 61 | 62 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 63 | http://www.apache.org/licenses/LICENSE-2.0) 64 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 65 | 66 | at your option. 67 | 68 | ### Contribution 69 | 70 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the 71 | work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 72 | additional terms or conditions. 73 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), String> { 2 | if cfg!(feature = "esp32") { 3 | match std::env::var("OPT_LEVEL") { 4 | Ok(level) => { 5 | if level != "2" && level != "3" { 6 | Err(format!("Building esp-storage for ESP32 needs optimization level 2 or 3 - yours is {}. See https://github.com/esp-rs/esp-storage", level)) 7 | } else { 8 | Ok(()) 9 | } 10 | } 11 | Err(_err) => Ok(()), 12 | } 13 | } else { 14 | Ok(()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/demo.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embedded_storage::{ReadStorage, Storage}; 5 | #[cfg(feature = "esp32")] 6 | use esp32_hal as hal; 7 | 8 | #[cfg(feature = "esp32s2")] 9 | use esp32s2_hal as hal; 10 | 11 | #[cfg(feature = "esp32s3")] 12 | use esp32s3_hal as hal; 13 | 14 | #[cfg(feature = "esp32c3")] 15 | use esp32c3_hal as hal; 16 | 17 | #[cfg(feature = "esp32c2")] 18 | use esp32c2_hal as hal; 19 | 20 | #[cfg(feature = "esp32c6")] 21 | use esp32c6_hal as hal; 22 | 23 | #[cfg(feature = "esp32h2")] 24 | use esp32h2_hal as hal; 25 | 26 | use hal::prelude::*; 27 | 28 | use esp_storage::FlashStorage; 29 | 30 | use esp_backtrace as _; 31 | use esp_println::println; 32 | 33 | #[entry] 34 | fn main() -> ! { 35 | let mut bytes = [0u8; 32]; 36 | 37 | let mut flash = FlashStorage::new(); 38 | 39 | let flash_addr = 0x9000; 40 | println!("Flash size = {}", flash.capacity()); 41 | 42 | flash.read(flash_addr, &mut bytes).unwrap(); 43 | println!("Read from {:x}: {:02x?}", flash_addr, &bytes[..32]); 44 | 45 | bytes[0x00] = bytes[0x00].wrapping_add(1); 46 | bytes[0x01] = bytes[0x01].wrapping_add(2); 47 | bytes[0x02] = bytes[0x02].wrapping_add(3); 48 | bytes[0x03] = bytes[0x03].wrapping_add(4); 49 | bytes[0x04] = bytes[0x04].wrapping_add(1); 50 | bytes[0x05] = bytes[0x05].wrapping_add(2); 51 | bytes[0x06] = bytes[0x06].wrapping_add(3); 52 | bytes[0x07] = bytes[0x07].wrapping_add(4); 53 | 54 | flash.write(flash_addr, &bytes).unwrap(); 55 | println!("Written to {:x}: {:02x?}", flash_addr, &bytes[..32]); 56 | 57 | let mut reread_bytes = [0u8; 32]; 58 | flash.read(flash_addr, &mut reread_bytes).unwrap(); 59 | println!("Read from {:x}: {:02x?}", flash_addr, &reread_bytes[..32]); 60 | 61 | loop {} 62 | } 63 | -------------------------------------------------------------------------------- /examples/low_level.rs: -------------------------------------------------------------------------------- 1 | //! This shows usage of the underlying low-level API 2 | //! 3 | //! Using this API is generally discouraged and reserved to a few very special use-cases. 4 | //! 5 | 6 | #![no_std] 7 | #![no_main] 8 | 9 | #[cfg(feature = "esp32")] 10 | use esp32_hal as hal; 11 | 12 | #[cfg(feature = "esp32s2")] 13 | use esp32s2_hal as hal; 14 | 15 | #[cfg(feature = "esp32s3")] 16 | use esp32s3_hal as hal; 17 | 18 | #[cfg(feature = "esp32c3")] 19 | use esp32c3_hal as hal; 20 | 21 | #[cfg(feature = "esp32c2")] 22 | use esp32c2_hal as hal; 23 | 24 | #[cfg(feature = "esp32c6")] 25 | use esp32c6_hal as hal; 26 | 27 | #[cfg(feature = "esp32h2")] 28 | use esp32h2_hal as hal; 29 | 30 | use hal::prelude::*; 31 | 32 | use esp_backtrace as _; 33 | use esp_println::println; 34 | 35 | #[entry] 36 | fn main() -> ! { 37 | let mut bytes = [0u8; 48]; 38 | let flash_addr = 0x9000; 39 | 40 | unsafe { 41 | esp_storage::ll::spiflash_read( 42 | flash_addr, 43 | bytes.as_mut_ptr() as *mut u32, 44 | bytes.len() as u32, 45 | ) 46 | } 47 | .unwrap(); 48 | 49 | println!("Read from {:x}: {:02x?}", flash_addr, &bytes[..48]); 50 | 51 | bytes[0x00] = bytes[0x00].wrapping_add(1); 52 | bytes[0x01] = bytes[0x01].wrapping_add(2); 53 | bytes[0x02] = bytes[0x02].wrapping_add(3); 54 | bytes[0x03] = bytes[0x03].wrapping_add(4); 55 | bytes[0x04] = bytes[0x04].wrapping_add(1); 56 | bytes[0x05] = bytes[0x05].wrapping_add(2); 57 | bytes[0x06] = bytes[0x06].wrapping_add(3); 58 | bytes[0x07] = bytes[0x07].wrapping_add(4); 59 | 60 | unsafe { esp_storage::ll::spiflash_unlock() }.unwrap(); 61 | 62 | unsafe { esp_storage::ll::spiflash_erase_sector(flash_addr / 4096) }.unwrap(); 63 | 64 | unsafe { 65 | esp_storage::ll::spiflash_write( 66 | flash_addr, 67 | bytes.as_ptr() as *const u32, 68 | bytes.len() as u32, 69 | ) 70 | } 71 | .unwrap(); 72 | println!("Written to {:x}: {:02x?}", flash_addr, &bytes[..48]); 73 | 74 | let mut reread_bytes = [0u8; 48]; 75 | unsafe { 76 | esp_storage::ll::spiflash_read( 77 | flash_addr, 78 | reread_bytes.as_mut_ptr() as *mut u32, 79 | reread_bytes.len() as u32, 80 | ) 81 | } 82 | .unwrap(); 83 | println!("Read from {:x}: {:02x?}", flash_addr, &reread_bytes[..48]); 84 | 85 | loop {} 86 | } 87 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use crate::chip_specific; 2 | use core::ops::{Deref, DerefMut}; 3 | 4 | #[repr(C, align(4))] 5 | pub struct FlashSectorBuffer { 6 | // NOTE: Ensure that no unaligned fields are added above `data` to maintain its required alignment 7 | data: [u8; FlashStorage::SECTOR_SIZE as usize], 8 | } 9 | 10 | impl Deref for FlashSectorBuffer { 11 | type Target = [u8; FlashStorage::SECTOR_SIZE as usize]; 12 | 13 | fn deref(&self) -> &Self::Target { 14 | &self.data 15 | } 16 | } 17 | 18 | impl DerefMut for FlashSectorBuffer { 19 | fn deref_mut(&mut self) -> &mut Self::Target { 20 | &mut self.data 21 | } 22 | } 23 | 24 | #[derive(Debug)] 25 | #[non_exhaustive] 26 | pub enum FlashStorageError { 27 | IoError, 28 | IoTimeout, 29 | CantUnlock, 30 | NotAligned, 31 | OutOfBounds, 32 | Other(i32), 33 | } 34 | 35 | #[inline(always)] 36 | pub fn check_rc(rc: i32) -> Result<(), FlashStorageError> { 37 | match rc { 38 | 0 => Ok(()), 39 | 1 => Err(FlashStorageError::IoError), 40 | 2 => Err(FlashStorageError::IoTimeout), 41 | _ => Err(FlashStorageError::Other(rc)), 42 | } 43 | } 44 | 45 | #[derive(Debug)] 46 | pub struct FlashStorage { 47 | pub(crate) capacity: usize, 48 | unlocked: bool, 49 | } 50 | 51 | impl Default for FlashStorage { 52 | fn default() -> Self { 53 | Self::new() 54 | } 55 | } 56 | 57 | impl FlashStorage { 58 | pub const WORD_SIZE: u32 = 4; 59 | pub const SECTOR_SIZE: u32 = 4096; 60 | 61 | pub fn new() -> FlashStorage { 62 | let mut storage = FlashStorage { 63 | capacity: 0, 64 | unlocked: false, 65 | }; 66 | 67 | #[cfg(not(any(feature = "esp32", feature = "esp32s2")))] 68 | const ADDR: u32 = 0x0000; 69 | #[cfg(any(feature = "esp32", feature = "esp32s2"))] 70 | const ADDR: u32 = 0x1000; 71 | 72 | let mut buffer = [0u8; 8]; 73 | storage.internal_read(ADDR, &mut buffer).ok(); 74 | let mb = match buffer[3] & 0xf0 { 75 | 0x00 => 1, 76 | 0x10 => 2, 77 | 0x20 => 4, 78 | 0x30 => 8, 79 | 0x40 => 16, 80 | _ => 0, 81 | }; 82 | storage.capacity = mb * 1024 * 1024; 83 | 84 | storage 85 | } 86 | 87 | #[cfg(feature = "nor-flash")] 88 | #[inline(always)] 89 | pub(crate) fn check_alignment( 90 | &self, 91 | offset: u32, 92 | length: usize, 93 | ) -> Result<(), FlashStorageError> { 94 | let offset = offset as usize; 95 | if offset % ALIGN as usize != 0 || length % ALIGN as usize != 0 { 96 | return Err(FlashStorageError::NotAligned); 97 | } 98 | Ok(()) 99 | } 100 | 101 | #[inline(always)] 102 | pub(crate) fn check_bounds(&self, offset: u32, length: usize) -> Result<(), FlashStorageError> { 103 | let offset = offset as usize; 104 | if length > self.capacity || offset > self.capacity - length { 105 | return Err(FlashStorageError::OutOfBounds); 106 | } 107 | Ok(()) 108 | } 109 | 110 | #[allow(clippy::all)] 111 | #[inline(never)] 112 | #[link_section = ".rwtext"] 113 | pub(crate) fn internal_read( 114 | &mut self, 115 | offset: u32, 116 | bytes: &mut [u8], 117 | ) -> Result<(), FlashStorageError> { 118 | check_rc(chip_specific::esp_rom_spiflash_read( 119 | offset, 120 | bytes.as_ptr() as *mut u32, 121 | bytes.len() as u32, 122 | )) 123 | } 124 | 125 | #[inline(always)] 126 | fn unlock_once(&mut self) -> Result<(), FlashStorageError> { 127 | if !self.unlocked { 128 | if chip_specific::esp_rom_spiflash_unlock() != 0 { 129 | return Err(FlashStorageError::CantUnlock); 130 | } 131 | self.unlocked = true; 132 | } 133 | Ok(()) 134 | } 135 | 136 | #[inline(never)] 137 | #[link_section = ".rwtext"] 138 | pub(crate) fn internal_erase(&mut self, sector: u32) -> Result<(), FlashStorageError> { 139 | self.unlock_once()?; 140 | 141 | check_rc(chip_specific::esp_rom_spiflash_erase_sector(sector)) 142 | } 143 | 144 | #[inline(never)] 145 | #[link_section = ".rwtext"] 146 | pub(crate) fn internal_write( 147 | &mut self, 148 | offset: u32, 149 | bytes: &[u8], 150 | ) -> Result<(), FlashStorageError> { 151 | self.unlock_once()?; 152 | 153 | check_rc(chip_specific::esp_rom_spiflash_write( 154 | offset, 155 | bytes.as_ptr() as *const u32, 156 | bytes.len() as u32, 157 | )) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/esp32.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_with_critical_section; 2 | 3 | const ESP_ROM_SPIFLASH_READ: u32 = 0x40062ed8; 4 | const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40062ccc; 5 | const SPI_READ_STATUS_HIGH: u32 = 0x40062448; 6 | const SPI_READ_STATUS: u32 = 0x4006226c; 7 | const SPI_WRITE_STATUS: u32 = 0x400622f0; 8 | 9 | const CACHE_FLUSH_ROM: u32 = 0x40009a14; 10 | const CACHE_READ_ENABLE_ROM: u32 = 0x40009a84; 11 | 12 | const SPI_BASE_REG: u32 = 0x3ff42000; /* SPI peripheral 1, used for SPI flash */ 13 | const SPI0_BASE_REG: u32 = 0x3ff43000; /* SPI peripheral 0, inner state machine */ 14 | const SPI_EXT2_REG: u32 = SPI_BASE_REG + 0xF8; 15 | const SPI0_EXT2_REG: u32 = SPI0_BASE_REG + 0xF8; 16 | const SPI_RD_STATUS_REG: u32 = SPI_BASE_REG + 0x10; 17 | #[allow(clippy::identity_op)] 18 | const SPI_CMD_REG: u32 = SPI_BASE_REG + 0x00; 19 | const SPI_CTRL_REG: u32 = SPI_BASE_REG + 0x08; 20 | const SPI_USER_REG: u32 = SPI_BASE_REG + 0x1c; 21 | const SPI_USER1_REG: u32 = SPI_BASE_REG + 0x20; 22 | const SPI_ADDR_REG: u32 = SPI_BASE_REG + 4; 23 | const SPI_W0_REG: u32 = SPI_BASE_REG + 0x80; 24 | const SPI_ST: u32 = 0x7; 25 | const SPI_USR_DUMMY: u32 = 1 << 29; 26 | const ESP_ROM_SPIFLASH_W_SIO_ADDR_BITSLEN: u32 = 23; 27 | const SPI_USR_ADDR_BITLEN_M: u32 = 0x3f << 26; 28 | const SPI_USR_ADDR_BITLEN_S: u32 = 26; 29 | const SPI_FLASH_WREN: u32 = 1 << 30; 30 | const STATUS_WIP_BIT: u32 = 1 << 0; 31 | const STATUS_QIE_BIT: u32 = 1 << 9; /* Quad Enable */ 32 | const SPI_WRSR_2B: u32 = 1 << 22; 33 | 34 | const FLASH_CHIP_ADDR: u32 = 0x3ffae270; 35 | const FLASH_DUMMY_LEN_PLUS_ADDR: u32 = 0x3ffae290; 36 | 37 | #[inline(always)] 38 | #[link_section = ".rwtext"] 39 | pub(crate) fn cache_flush_rom(cpu_num: u32) { 40 | unsafe { 41 | let cache_flush_rom: unsafe extern "C" fn(u32) = core::mem::transmute(CACHE_FLUSH_ROM); 42 | cache_flush_rom(cpu_num) 43 | } 44 | } 45 | 46 | #[inline(always)] 47 | #[link_section = ".rwtext"] 48 | pub(crate) fn cache_read_enable_rom(cpu_num: u32) { 49 | unsafe { 50 | let cache_read_enable_rom: unsafe extern "C" fn(u32) = 51 | core::mem::transmute(CACHE_READ_ENABLE_ROM); 52 | cache_read_enable_rom(cpu_num) 53 | } 54 | } 55 | 56 | #[inline(always)] 57 | #[link_section = ".rwtext"] 58 | pub(crate) fn spi_read_status_high( 59 | flash_chip: *const EspRomSpiflashChipT, 60 | status: &mut u32, 61 | ) -> i32 { 62 | unsafe { 63 | let spi_read_status_high: unsafe extern "C" fn( 64 | *const EspRomSpiflashChipT, 65 | *mut u32, 66 | ) -> i32 = core::mem::transmute(SPI_READ_STATUS_HIGH); 67 | spi_read_status_high(flash_chip, status as *mut u32) 68 | } 69 | } 70 | 71 | #[inline(always)] 72 | #[link_section = ".rwtext"] 73 | pub(crate) fn spi_read_status(flash_chip: *const EspRomSpiflashChipT, status: &mut u32) -> i32 { 74 | unsafe { 75 | let spi_read_status: unsafe extern "C" fn(*const EspRomSpiflashChipT, *mut u32) -> i32 = 76 | core::mem::transmute(SPI_READ_STATUS); 77 | spi_read_status(flash_chip, status as *mut u32) 78 | } 79 | } 80 | 81 | #[inline(always)] 82 | #[link_section = ".rwtext"] 83 | pub(crate) fn spi_write_status(flash_chip: *const EspRomSpiflashChipT, status_value: u32) -> i32 { 84 | unsafe { 85 | let spi_write_status: unsafe extern "C" fn(*const EspRomSpiflashChipT, u32) -> i32 = 86 | core::mem::transmute(SPI_WRITE_STATUS); 87 | spi_write_status(flash_chip, status_value) 88 | } 89 | } 90 | 91 | #[inline(always)] 92 | #[link_section = ".rwtext"] 93 | fn begin() { 94 | // on some chips disabling cache access caused issues - we don't really need it 95 | } 96 | 97 | #[inline(always)] 98 | #[link_section = ".rwtext"] 99 | fn end() { 100 | cache_flush_rom(0); 101 | cache_flush_rom(1); 102 | cache_read_enable_rom(0); 103 | cache_read_enable_rom(1); 104 | } 105 | 106 | #[derive(Debug)] 107 | #[repr(C)] 108 | pub struct EspRomSpiflashChipT { 109 | device_id: u32, 110 | chip_size: u32, // chip size in bytes 111 | block_size: u32, 112 | sector_size: u32, 113 | page_size: u32, 114 | status_mask: u32, 115 | } 116 | 117 | #[inline(never)] 118 | #[link_section = ".rwtext"] 119 | pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { 120 | maybe_with_critical_section(|| { 121 | spiflash_wait_for_ready(); 122 | unsafe { 123 | let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 124 | core::mem::transmute(ESP_ROM_SPIFLASH_READ); 125 | esp_rom_spiflash_read(src_addr, data, len) 126 | } 127 | }) 128 | } 129 | 130 | #[inline(never)] 131 | #[link_section = ".rwtext"] 132 | pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { 133 | maybe_with_critical_section(|| { 134 | let res = unsafe { 135 | let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = 136 | core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); 137 | esp_rom_spiflash_erase_sector(sector_number) 138 | }; 139 | spiflash_wait_for_ready(); 140 | res 141 | }) 142 | } 143 | 144 | #[inline(always)] 145 | #[link_section = ".rwtext"] 146 | fn spi_write_enable() { 147 | spiflash_wait_for_ready(); 148 | 149 | write_register(SPI_RD_STATUS_REG, 0); 150 | write_register(SPI_CMD_REG, SPI_FLASH_WREN); 151 | while read_register(SPI_CMD_REG) != 0 {} 152 | } 153 | 154 | #[inline(never)] 155 | #[link_section = ".rwtext"] 156 | pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { 157 | maybe_with_critical_section(|| { 158 | begin(); 159 | 160 | let flashchip = FLASH_CHIP_ADDR as *const EspRomSpiflashChipT; 161 | let mut status: u32 = 0; 162 | 163 | spiflash_wait_for_ready(); 164 | if spi_read_status_high(flashchip, &mut status) != 0 { 165 | return -1; 166 | } 167 | 168 | spiflash_wait_for_ready(); 169 | 170 | write_register(SPI_USER_REG, read_register(SPI_USER_REG) & !SPI_USR_DUMMY); 171 | let addrbits = ESP_ROM_SPIFLASH_W_SIO_ADDR_BITSLEN; 172 | let mut regval = read_register(SPI_USER1_REG); 173 | regval &= !SPI_USR_ADDR_BITLEN_M; 174 | regval |= addrbits << SPI_USR_ADDR_BITLEN_S; 175 | write_register(SPI_USER1_REG, regval); 176 | 177 | for block in (0..len).step_by(32) { 178 | spiflash_wait_for_ready(); 179 | spi_write_enable(); 180 | 181 | let block_len = if len - block < 32 { len - block } else { 32 }; 182 | write_register( 183 | SPI_ADDR_REG, 184 | ((dest_addr + block) & 0xffffff) | block_len << 24, 185 | ); 186 | 187 | let data_ptr = unsafe { data.offset((block / 4) as isize) }; 188 | for i in 0..block_len / 4 { 189 | write_register(SPI_W0_REG + (4 * i), unsafe { 190 | data_ptr.offset(i as isize).read_volatile() 191 | }); 192 | } 193 | 194 | write_register(SPI_RD_STATUS_REG, 0); 195 | write_register(SPI_CMD_REG, 1 << 25); // FLASH PP 196 | while read_register(SPI_CMD_REG) != 0 { /* wait */ } 197 | 198 | wait_for_ready(); 199 | } 200 | 201 | spiflash_wait_for_ready(); 202 | if spi_write_status(flashchip, status) != 0 { 203 | end(); 204 | return -1; 205 | } 206 | spiflash_wait_for_ready(); 207 | 208 | end(); 209 | 0 210 | }) 211 | } 212 | 213 | #[inline(always)] 214 | #[link_section = ".rwtext"] 215 | pub fn read_register(address: u32) -> u32 { 216 | unsafe { (address as *const u32).read_volatile() } 217 | } 218 | 219 | #[inline(always)] 220 | #[link_section = ".rwtext"] 221 | pub fn write_register(address: u32, value: u32) { 222 | unsafe { 223 | (address as *mut u32).write_volatile(value); 224 | } 225 | } 226 | 227 | #[inline(always)] 228 | #[link_section = ".rwtext"] 229 | fn wait_for_ready() { 230 | while (read_register(SPI_EXT2_REG) & SPI_ST) != 0 {} 231 | // ESP32_OR_LATER ... we don't support anything earlier 232 | while (read_register(SPI0_EXT2_REG) & SPI_ST) != 0 {} 233 | } 234 | 235 | #[inline(always)] 236 | #[link_section = ".rwtext"] 237 | fn spiflash_wait_for_ready() { 238 | let flashchip = FLASH_CHIP_ADDR as *const EspRomSpiflashChipT; 239 | 240 | loop { 241 | wait_for_ready(); 242 | let mut status = 0; 243 | spi_read_status(flashchip, &mut status); 244 | if status & STATUS_WIP_BIT == 0 { 245 | break; 246 | } 247 | } 248 | } 249 | 250 | #[inline(never)] 251 | #[link_section = ".rwtext"] 252 | pub(crate) fn esp_rom_spiflash_unlock() -> i32 { 253 | let flashchip = FLASH_CHIP_ADDR as *const EspRomSpiflashChipT; 254 | if unsafe { (*flashchip).device_id } >> 16 & 0xff == 0x9D { 255 | panic!("ISSI flash is not supported"); 256 | } 257 | 258 | let g_rom_spiflash_dummy_len_plus = FLASH_DUMMY_LEN_PLUS_ADDR as *const u8; 259 | if unsafe { g_rom_spiflash_dummy_len_plus.add(1).read_volatile() } == 0 { 260 | panic!("Unsupported flash chip"); 261 | } 262 | 263 | maybe_with_critical_section(|| { 264 | begin(); 265 | spiflash_wait_for_ready(); 266 | 267 | let mut status: u32 = 0; 268 | if spi_read_status_high(flashchip, &mut status) != 0 { 269 | return -1; 270 | } 271 | 272 | // Clear all bits except QE, if it is set 273 | status &= STATUS_QIE_BIT; 274 | 275 | write_register(SPI_CTRL_REG, read_register(SPI_CTRL_REG) | SPI_WRSR_2B); 276 | 277 | spiflash_wait_for_ready(); 278 | if spi_write_status(flashchip, status) != 0 { 279 | end(); 280 | return -1; 281 | } 282 | spiflash_wait_for_ready(); 283 | end(); 284 | 0 285 | }) 286 | } 287 | -------------------------------------------------------------------------------- /src/esp32c2.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_with_critical_section; 2 | 3 | const ESP_ROM_SPIFLASH_READ: u32 = 0x4000013c; 4 | const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000140; 5 | const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40000130; 6 | const ESP_ROM_SPIFLASH_WRITE: u32 = 0x40000138; 7 | 8 | pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { 9 | maybe_with_critical_section(|| unsafe { 10 | let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 11 | core::mem::transmute(ESP_ROM_SPIFLASH_READ); 12 | esp_rom_spiflash_read(src_addr, data, len) 13 | }) 14 | } 15 | 16 | pub(crate) fn esp_rom_spiflash_unlock() -> i32 { 17 | maybe_with_critical_section(|| unsafe { 18 | let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = 19 | core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); 20 | esp_rom_spiflash_unlock() 21 | }) 22 | } 23 | 24 | pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { 25 | maybe_with_critical_section(|| unsafe { 26 | let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = 27 | core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); 28 | esp_rom_spiflash_erase_sector(sector_number) 29 | }) 30 | } 31 | 32 | pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { 33 | maybe_with_critical_section(|| unsafe { 34 | let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 35 | core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); 36 | esp_rom_spiflash_write(dest_addr, data, len) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/esp32c3.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_with_critical_section; 2 | 3 | const ESP_ROM_SPIFLASH_READ: u32 = 0x40000130; 4 | const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000140; 5 | const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40000128; 6 | const ESP_ROM_SPIFLASH_WRITE: u32 = 0x4000012c; 7 | 8 | pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { 9 | maybe_with_critical_section(|| unsafe { 10 | let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 11 | core::mem::transmute(ESP_ROM_SPIFLASH_READ); 12 | esp_rom_spiflash_read(src_addr, data, len) 13 | }) 14 | } 15 | 16 | pub(crate) fn esp_rom_spiflash_unlock() -> i32 { 17 | maybe_with_critical_section(|| unsafe { 18 | let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = 19 | core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); 20 | esp_rom_spiflash_unlock() 21 | }) 22 | } 23 | 24 | pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { 25 | maybe_with_critical_section(|| unsafe { 26 | let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = 27 | core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); 28 | esp_rom_spiflash_erase_sector(sector_number) 29 | }) 30 | } 31 | 32 | pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { 33 | maybe_with_critical_section(|| unsafe { 34 | let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 35 | core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); 36 | esp_rom_spiflash_write(dest_addr, data, len) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/esp32c6.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_with_critical_section; 2 | 3 | const ESP_ROM_SPIFLASH_READ: u32 = 0x40000150; 4 | const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000154; 5 | const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40000144; 6 | const ESP_ROM_SPIFLASH_WRITE: u32 = 0x4000014c; 7 | 8 | pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { 9 | maybe_with_critical_section(|| unsafe { 10 | let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 11 | core::mem::transmute(ESP_ROM_SPIFLASH_READ); 12 | esp_rom_spiflash_read(src_addr, data, len) 13 | }) 14 | } 15 | 16 | pub(crate) fn esp_rom_spiflash_unlock() -> i32 { 17 | maybe_with_critical_section(|| unsafe { 18 | let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = 19 | core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); 20 | esp_rom_spiflash_unlock() 21 | }) 22 | } 23 | 24 | pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { 25 | maybe_with_critical_section(|| unsafe { 26 | let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = 27 | core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); 28 | esp_rom_spiflash_erase_sector(sector_number) 29 | }) 30 | } 31 | 32 | pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { 33 | maybe_with_critical_section(|| unsafe { 34 | let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 35 | core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); 36 | esp_rom_spiflash_write(dest_addr, data, len) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/esp32h2.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_with_critical_section; 2 | 3 | const ESP_ROM_SPIFLASH_READ: u32 = 0x4000012c; 4 | const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000130; 5 | const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40000120; 6 | const ESP_ROM_SPIFLASH_WRITE: u32 = 0x40000128; 7 | 8 | pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { 9 | maybe_with_critical_section(|| unsafe { 10 | let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 11 | core::mem::transmute(ESP_ROM_SPIFLASH_READ); 12 | esp_rom_spiflash_read(src_addr, data, len) 13 | }) 14 | } 15 | 16 | pub(crate) fn esp_rom_spiflash_unlock() -> i32 { 17 | maybe_with_critical_section(|| unsafe { 18 | let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = 19 | core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); 20 | esp_rom_spiflash_unlock() 21 | }) 22 | } 23 | 24 | pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { 25 | maybe_with_critical_section(|| unsafe { 26 | let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = 27 | core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); 28 | esp_rom_spiflash_erase_sector(sector_number) 29 | }) 30 | } 31 | 32 | pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { 33 | maybe_with_critical_section(|| unsafe { 34 | let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 35 | core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); 36 | esp_rom_spiflash_write(dest_addr, data, len) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/esp32s2.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_with_critical_section; 2 | 3 | const ESP_ROM_SPIFLASH_READ: u32 = 0x4001728c; 4 | const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40016e88; 5 | const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x4001716c; 6 | const ESP_ROM_SPIFLASH_WRITE: u32 = 0x400171cc; 7 | 8 | pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { 9 | maybe_with_critical_section(|| unsafe { 10 | let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 11 | core::mem::transmute(ESP_ROM_SPIFLASH_READ); 12 | esp_rom_spiflash_read(src_addr, data, len) 13 | }) 14 | } 15 | 16 | pub(crate) fn esp_rom_spiflash_unlock() -> i32 { 17 | maybe_with_critical_section(|| unsafe { 18 | let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = 19 | core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); 20 | esp_rom_spiflash_unlock() 21 | }) 22 | } 23 | 24 | pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { 25 | maybe_with_critical_section(|| unsafe { 26 | let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = 27 | core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); 28 | esp_rom_spiflash_erase_sector(sector_number) 29 | }) 30 | } 31 | 32 | pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { 33 | maybe_with_critical_section(|| unsafe { 34 | let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 35 | core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); 36 | esp_rom_spiflash_write(dest_addr, data, len) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/esp32s3.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_with_critical_section; 2 | 3 | const ESP_ROM_SPIFLASH_READ: u32 = 0x40000a20; 4 | const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000a2c; 5 | const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x400009fc; 6 | const ESP_ROM_SPIFLASH_WRITE: u32 = 0x40000a14; 7 | 8 | #[inline(always)] 9 | #[link_section = ".rwtext"] 10 | pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { 11 | maybe_with_critical_section(|| unsafe { 12 | let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 13 | core::mem::transmute(ESP_ROM_SPIFLASH_READ); 14 | esp_rom_spiflash_read(src_addr, data, len) 15 | }) 16 | } 17 | 18 | #[inline(always)] 19 | #[link_section = ".rwtext"] 20 | pub(crate) fn esp_rom_spiflash_unlock() -> i32 { 21 | maybe_with_critical_section(|| unsafe { 22 | let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = 23 | core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); 24 | esp_rom_spiflash_unlock() 25 | }) 26 | } 27 | 28 | #[inline(always)] 29 | #[link_section = ".rwtext"] 30 | pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { 31 | maybe_with_critical_section(|| unsafe { 32 | let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = 33 | core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); 34 | esp_rom_spiflash_erase_sector(sector_number) 35 | }) 36 | } 37 | 38 | #[inline(always)] 39 | #[link_section = ".rwtext"] 40 | pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { 41 | maybe_with_critical_section(|| unsafe { 42 | let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = 43 | core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); 44 | esp_rom_spiflash_write(dest_addr, data, len) 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(all(test, feature = "emulation")), no_std)] 2 | 3 | #[cfg(not(feature = "emulation"))] 4 | #[cfg_attr(feature = "esp32c2", path = "esp32c2.rs")] 5 | #[cfg_attr(feature = "esp32c3", path = "esp32c3.rs")] 6 | #[cfg_attr(feature = "esp32c6", path = "esp32c6.rs")] 7 | #[cfg_attr(feature = "esp32h2", path = "esp32h2.rs")] 8 | #[cfg_attr(feature = "esp32", path = "esp32.rs")] 9 | #[cfg_attr(feature = "esp32s2", path = "esp32s2.rs")] 10 | #[cfg_attr(feature = "esp32s3", path = "esp32s3.rs")] 11 | #[cfg_attr( 12 | not(any( 13 | feature = "esp32c2", 14 | feature = "esp32c3", 15 | feature = "esp32c6", 16 | feature = "esp32", 17 | feature = "esp32s2", 18 | feature = "esp32s3", 19 | feature = "esp32h2" 20 | )), 21 | path = "stub.rs" 22 | )] 23 | mod chip_specific; 24 | 25 | #[cfg(feature = "emulation")] 26 | #[path = "stub.rs"] 27 | mod chip_specific; 28 | 29 | #[cfg(any(feature = "storage", feature = "nor-flash"))] 30 | mod common; 31 | 32 | #[cfg(any(feature = "storage", feature = "nor-flash"))] 33 | pub use common::{FlashStorage, FlashStorageError}; 34 | 35 | #[cfg(any(feature = "storage", feature = "nor-flash"))] 36 | use common::FlashSectorBuffer; 37 | 38 | #[cfg(feature = "storage")] 39 | mod storage; 40 | 41 | #[cfg(feature = "nor-flash")] 42 | mod nor_flash; 43 | 44 | #[cfg(feature = "low-level")] 45 | pub mod ll; 46 | 47 | #[cfg(not(feature = "emulation"))] 48 | #[inline(always)] 49 | #[link_section = ".rwtext"] 50 | fn maybe_with_critical_section(f: impl FnOnce() -> R) -> R { 51 | #[cfg(feature = "critical-section")] 52 | return critical_section::with(|_| f()); 53 | 54 | #[cfg(not(feature = "critical-section"))] 55 | f() 56 | } 57 | 58 | #[cfg(feature = "emulation")] 59 | fn maybe_with_critical_section(f: impl FnOnce() -> R) -> R { 60 | f() 61 | } 62 | -------------------------------------------------------------------------------- /src/ll.rs: -------------------------------------------------------------------------------- 1 | /// Low-level API 2 | /// 3 | /// This gives you access to the underlying low level functionality. 4 | /// These operate on raw pointers and all functions here are unsafe. 5 | /// No pre-conditions are checked by any of these functions. 6 | use crate::chip_specific; 7 | 8 | /// Low-level SPI NOR Flash read 9 | /// 10 | /// # Safety 11 | /// 12 | /// The `src_addr` + `len` should not exceeds the size of flash. 13 | /// The `data` expected to points to word-aligned pre-allocated buffer with size greater or equals to `len`. 14 | pub unsafe fn spiflash_read(src_addr: u32, data: *mut u32, len: u32) -> Result<(), i32> { 15 | match chip_specific::esp_rom_spiflash_read(src_addr, data, len) { 16 | 0 => Ok(()), 17 | value => Err(value), 18 | } 19 | } 20 | 21 | /// Low-level SPI NOR Flash unlock 22 | /// 23 | /// # Safety 24 | pub unsafe fn spiflash_unlock() -> Result<(), i32> { 25 | match chip_specific::esp_rom_spiflash_unlock() { 26 | 0 => Ok(()), 27 | value => Err(value), 28 | } 29 | } 30 | 31 | /// Low-level SPI NOR Flash erase 32 | /// 33 | /// # Safety 34 | /// 35 | /// The `sector_number` * sector_size should not exceeds the size of flash. 36 | pub unsafe fn spiflash_erase_sector(sector_number: u32) -> Result<(), i32> { 37 | match chip_specific::esp_rom_spiflash_erase_sector(sector_number) { 38 | 0 => Ok(()), 39 | value => Err(value), 40 | } 41 | } 42 | 43 | /// Low-level SPI NOR Flash write 44 | /// 45 | /// # Safety 46 | /// 47 | /// The `dest_addr` + `len` should not exceeds the size of flash. 48 | /// The `data` expected to points to word-aligned buffer with size greater or equals to `len`. 49 | pub unsafe fn spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> Result<(), i32> { 50 | match chip_specific::esp_rom_spiflash_write(dest_addr, data, len) { 51 | 0 => Ok(()), 52 | value => Err(value), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/nor_flash.rs: -------------------------------------------------------------------------------- 1 | use crate::{FlashSectorBuffer, FlashStorage, FlashStorageError}; 2 | use core::{ 3 | mem::MaybeUninit, 4 | ops::{Deref, DerefMut}, 5 | }; 6 | use embedded_storage::nor_flash::{ 7 | ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, 8 | }; 9 | 10 | #[repr(C, align(4))] 11 | struct FlashWordBuffer { 12 | // NOTE: Ensure that no unaligned fields are added above `data` to maintain its required alignment 13 | data: [u8; FlashStorage::WORD_SIZE as usize], 14 | } 15 | 16 | impl Deref for FlashWordBuffer { 17 | type Target = [u8; FlashStorage::WORD_SIZE as usize]; 18 | 19 | fn deref(&self) -> &Self::Target { 20 | &self.data 21 | } 22 | } 23 | 24 | impl DerefMut for FlashWordBuffer { 25 | fn deref_mut(&mut self) -> &mut Self::Target { 26 | &mut self.data 27 | } 28 | } 29 | 30 | impl FlashStorage { 31 | #[inline(always)] 32 | fn is_word_aligned(bytes: &[u8]) -> bool { 33 | // TODO: Use is_aligned_to when stabilized (see `pointer_is_aligned`) 34 | (unsafe { bytes.as_ptr().offset_from(core::ptr::null()) }) % Self::WORD_SIZE as isize == 0 35 | } 36 | } 37 | 38 | impl NorFlashError for FlashStorageError { 39 | fn kind(&self) -> NorFlashErrorKind { 40 | match self { 41 | Self::NotAligned => NorFlashErrorKind::NotAligned, 42 | Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, 43 | _ => NorFlashErrorKind::Other, 44 | } 45 | } 46 | } 47 | 48 | impl ErrorType for FlashStorage { 49 | type Error = FlashStorageError; 50 | } 51 | 52 | impl ReadNorFlash for FlashStorage { 53 | #[cfg(not(feature = "bytewise-read"))] 54 | const READ_SIZE: usize = Self::WORD_SIZE as _; 55 | 56 | #[cfg(feature = "bytewise-read")] 57 | const READ_SIZE: usize = 1; 58 | 59 | fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { 60 | self.check_alignment::<{ Self::READ_SIZE as _ }>(offset, bytes.len())?; 61 | self.check_bounds(offset, bytes.len())?; 62 | 63 | #[cfg(feature = "bytewise-read")] 64 | let (offset, bytes) = { 65 | let byte_offset = (offset % Self::WORD_SIZE) as usize; 66 | if byte_offset > 0 { 67 | let mut word_buffer = MaybeUninit::::uninit(); 68 | let word_buffer = unsafe { word_buffer.assume_init_mut() }; 69 | 70 | let offset = offset - byte_offset as u32; 71 | let length = bytes.len().min(word_buffer.len() - byte_offset); 72 | 73 | self.internal_read(offset, &mut word_buffer[..])?; 74 | bytes[..length].copy_from_slice(&word_buffer[byte_offset..][..length]); 75 | 76 | (offset + Self::WORD_SIZE, &mut bytes[length..]) 77 | } else { 78 | (offset, bytes) 79 | } 80 | }; 81 | 82 | if Self::is_word_aligned(bytes) { 83 | // Bytes buffer is word-aligned so we can read directly to it 84 | for (offset, chunk) in (offset..) 85 | .step_by(Self::SECTOR_SIZE as _) 86 | .zip(bytes.chunks_mut(Self::SECTOR_SIZE as _)) 87 | { 88 | // Chunk already is word aligned so we can read directly to it 89 | #[cfg(not(feature = "bytewise-read"))] 90 | self.internal_read(offset, chunk)?; 91 | 92 | #[cfg(feature = "bytewise-read")] 93 | { 94 | let length = chunk.len(); 95 | let byte_length = length % Self::WORD_SIZE as usize; 96 | let length = length - byte_length; 97 | 98 | self.internal_read(offset, &mut chunk[..length])?; 99 | 100 | // Read not aligned rest of data 101 | if byte_length > 0 { 102 | let mut word_buffer = MaybeUninit::::uninit(); 103 | let word_buffer = unsafe { word_buffer.assume_init_mut() }; 104 | 105 | self.internal_read(offset + length as u32, &mut word_buffer[..])?; 106 | chunk[length..].copy_from_slice(&word_buffer[..byte_length]); 107 | } 108 | } 109 | } 110 | } else { 111 | // Bytes buffer isn't word-aligned so we might read only via aligned buffer 112 | let mut buffer = MaybeUninit::::uninit(); 113 | let buffer = unsafe { buffer.assume_init_mut() }; 114 | 115 | for (offset, chunk) in (offset..) 116 | .step_by(Self::SECTOR_SIZE as _) 117 | .zip(bytes.chunks_mut(Self::SECTOR_SIZE as _)) 118 | { 119 | // Read to temporary buffer first (chunk length is aligned) 120 | #[cfg(not(feature = "bytewise-read"))] 121 | self.internal_read(offset, &mut buffer[..chunk.len()])?; 122 | 123 | // Read to temporary buffer first (chunk length is not aligned) 124 | #[cfg(feature = "bytewise-read")] 125 | { 126 | let length = chunk.len(); 127 | let byte_length = length % Self::WORD_SIZE as usize; 128 | let length = if byte_length > 0 { 129 | length - byte_length + Self::WORD_SIZE as usize 130 | } else { 131 | length 132 | }; 133 | 134 | self.internal_read(offset, &mut buffer[..length])?; 135 | } 136 | 137 | // Copy to bytes buffer 138 | chunk.copy_from_slice(&buffer[..chunk.len()]); 139 | } 140 | } 141 | 142 | Ok(()) 143 | } 144 | 145 | fn capacity(&self) -> usize { 146 | self.capacity 147 | } 148 | } 149 | 150 | impl NorFlash for FlashStorage { 151 | const WRITE_SIZE: usize = Self::WORD_SIZE as _; 152 | const ERASE_SIZE: usize = Self::SECTOR_SIZE as _; 153 | 154 | fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { 155 | self.check_alignment::<{ Self::WORD_SIZE }>(offset, bytes.len())?; 156 | self.check_bounds(offset, bytes.len())?; 157 | 158 | if Self::is_word_aligned(bytes) { 159 | // Bytes buffer is word-aligned so we can write directly from it 160 | for (offset, chunk) in (offset..) 161 | .step_by(Self::SECTOR_SIZE as _) 162 | .zip(bytes.chunks(Self::SECTOR_SIZE as _)) 163 | { 164 | // Chunk already is word aligned so we can write directly from it 165 | self.internal_write(offset, chunk)?; 166 | } 167 | } else { 168 | // Bytes buffer isn't word-aligned so we might write only via aligned buffer 169 | let mut buffer = MaybeUninit::::uninit(); 170 | let buffer = unsafe { buffer.assume_init_mut() }; 171 | 172 | for (offset, chunk) in (offset..) 173 | .step_by(Self::SECTOR_SIZE as _) 174 | .zip(bytes.chunks(Self::SECTOR_SIZE as _)) 175 | { 176 | // Copy to temporary buffer first 177 | buffer[..chunk.len()].copy_from_slice(chunk); 178 | // Write from temporary buffer 179 | self.internal_write(offset, &buffer[..chunk.len()])?; 180 | } 181 | } 182 | 183 | Ok(()) 184 | } 185 | 186 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { 187 | let len = (to - from) as _; 188 | self.check_alignment::<{ Self::SECTOR_SIZE }>(from, len)?; 189 | self.check_bounds(from, len)?; 190 | 191 | for sector in from / Self::SECTOR_SIZE..to / Self::SECTOR_SIZE { 192 | self.internal_erase(sector)?; 193 | } 194 | 195 | Ok(()) 196 | } 197 | } 198 | 199 | #[cfg(test)] 200 | mod test { 201 | use super::*; 202 | use core::mem::MaybeUninit; 203 | 204 | const WORD_SIZE: u32 = 4; 205 | const SECTOR_SIZE: u32 = 4 << 10; 206 | const NUM_SECTORS: u32 = 3; 207 | const FLASH_SIZE: u32 = SECTOR_SIZE * NUM_SECTORS; 208 | const MAX_OFFSET: u32 = SECTOR_SIZE * 1; 209 | const MAX_LENGTH: u32 = SECTOR_SIZE * 2; 210 | 211 | #[repr(C, align(4))] 212 | struct TestBuffer { 213 | // NOTE: Ensure that no unaligned fields are added above `data` to maintain its required alignment 214 | data: MaybeUninit<[u8; FLASH_SIZE as _]>, 215 | } 216 | 217 | impl TestBuffer { 218 | const fn seq() -> Self { 219 | let mut data = [0u8; FLASH_SIZE as _]; 220 | let mut index = 0; 221 | while index < FLASH_SIZE { 222 | data[index as usize] = (index & 0xff) as u8; 223 | index += 1; 224 | } 225 | Self { 226 | data: MaybeUninit::new(data), 227 | } 228 | } 229 | } 230 | 231 | impl Default for TestBuffer { 232 | fn default() -> Self { 233 | Self { 234 | data: MaybeUninit::uninit(), 235 | } 236 | } 237 | } 238 | 239 | impl Deref for TestBuffer { 240 | type Target = [u8; FLASH_SIZE as usize]; 241 | 242 | fn deref(&self) -> &Self::Target { 243 | unsafe { self.data.assume_init_ref() } 244 | } 245 | } 246 | 247 | impl DerefMut for TestBuffer { 248 | fn deref_mut(&mut self) -> &mut Self::Target { 249 | unsafe { self.data.assume_init_mut() } 250 | } 251 | } 252 | 253 | fn range_gen( 254 | aligned: Option, 255 | ) -> impl Iterator { 256 | (0..=MAX_OFF).flat_map(move |off| { 257 | (0..=MAX_LEN - off) 258 | .filter(move |len| { 259 | aligned 260 | .map(|aligned| aligned == (off % ALIGN == 0 && len % ALIGN == 0)) 261 | .unwrap_or(true) 262 | }) 263 | .map(move |len| (off, len)) 264 | }) 265 | } 266 | 267 | #[test] 268 | #[cfg(not(feature = "bytewise-read"))] 269 | fn aligned_read() { 270 | let mut flash = FlashStorage::new(); 271 | let src = TestBuffer::seq(); 272 | let mut data = TestBuffer::default(); 273 | 274 | flash.erase(0, FLASH_SIZE).unwrap(); 275 | flash.write(0, &*src).unwrap(); 276 | 277 | for (off, len) in range_gen::(Some(true)) { 278 | flash.read(off, &mut data[..len as usize]).unwrap(); 279 | assert_eq!(data[..len as usize], src[off as usize..][..len as usize]); 280 | } 281 | } 282 | 283 | #[test] 284 | #[cfg(not(feature = "bytewise-read"))] 285 | fn not_aligned_read_aligned_buffer() { 286 | let mut flash = FlashStorage::new(); 287 | let mut data = TestBuffer::default(); 288 | 289 | for (off, len) in range_gen::(Some(false)) { 290 | flash.read(off, &mut data[..len as usize]).unwrap_err(); 291 | } 292 | } 293 | 294 | #[test] 295 | #[cfg(not(feature = "bytewise-read"))] 296 | fn aligned_read_not_aligned_buffer() { 297 | let mut flash = FlashStorage::new(); 298 | let src = TestBuffer::seq(); 299 | let mut data = TestBuffer::default(); 300 | 301 | flash.erase(0, FLASH_SIZE).unwrap(); 302 | flash.write(0, &*src).unwrap(); 303 | 304 | for (off, len) in range_gen::(Some(true)) { 305 | flash.read(off, &mut data[1..][..len as usize]).unwrap(); 306 | assert_eq!( 307 | data[1..][..len as usize], 308 | src[off as usize..][..len as usize] 309 | ); 310 | } 311 | } 312 | 313 | #[test] 314 | #[cfg(feature = "bytewise-read")] 315 | fn bytewise_read_aligned_buffer() { 316 | let mut flash = FlashStorage::new(); 317 | let src = TestBuffer::seq(); 318 | let mut data = TestBuffer::default(); 319 | 320 | flash.erase(0, FLASH_SIZE).unwrap(); 321 | flash.write(0, &*src).unwrap(); 322 | 323 | for (off, len) in range_gen::(None) { 324 | flash.read(off, &mut data[..len as usize]).unwrap(); 325 | assert_eq!(data[..len as usize], src[off as usize..][..len as usize]); 326 | } 327 | } 328 | 329 | #[test] 330 | #[cfg(feature = "bytewise-read")] 331 | fn bytewise_read_not_aligned_buffer() { 332 | let mut flash = FlashStorage::new(); 333 | let src = TestBuffer::seq(); 334 | let mut data = TestBuffer::default(); 335 | 336 | flash.erase(0, FLASH_SIZE).unwrap(); 337 | flash.write(0, &*src).unwrap(); 338 | 339 | for (off, len) in range_gen::(None) { 340 | flash.read(off, &mut data[1..][..len as usize]).unwrap(); 341 | assert_eq!( 342 | data[1..][..len as usize], 343 | src[off as usize..][..len as usize] 344 | ); 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::{FlashSectorBuffer, FlashStorage, FlashStorageError}; 2 | use core::mem::MaybeUninit; 3 | use embedded_storage::{ReadStorage, Storage}; 4 | 5 | impl ReadStorage for FlashStorage { 6 | type Error = FlashStorageError; 7 | 8 | fn read(&mut self, offset: u32, mut bytes: &mut [u8]) -> Result<(), Self::Error> { 9 | self.check_bounds(offset, bytes.len())?; 10 | 11 | let mut data_offset = offset % Self::WORD_SIZE; 12 | let mut aligned_offset = offset - data_offset; 13 | 14 | // Bypass clearing sector buffer for performance reasons 15 | let mut sector_data = MaybeUninit::::uninit(); 16 | let sector_data = unsafe { sector_data.assume_init_mut() }; 17 | 18 | while !bytes.is_empty() { 19 | let len = bytes.len().min((Self::SECTOR_SIZE - data_offset) as _); 20 | 21 | let aligned_end = (data_offset as usize + len + (Self::WORD_SIZE - 1) as usize) 22 | & !(Self::WORD_SIZE - 1) as usize; 23 | 24 | // Read only needed data words 25 | self.internal_read(aligned_offset, &mut sector_data[..aligned_end])?; 26 | 27 | bytes[..len].copy_from_slice(§or_data[data_offset as usize..][..len]); 28 | 29 | aligned_offset += Self::SECTOR_SIZE; 30 | data_offset = 0; 31 | bytes = &mut bytes[len..]; 32 | } 33 | 34 | Ok(()) 35 | } 36 | 37 | /// The SPI flash size is configured by writing a field in the software bootloader image header. 38 | /// This is done during flashing in espflash / esptool. 39 | fn capacity(&self) -> usize { 40 | self.capacity 41 | } 42 | } 43 | 44 | impl Storage for FlashStorage { 45 | fn write(&mut self, offset: u32, mut bytes: &[u8]) -> Result<(), Self::Error> { 46 | self.check_bounds(offset, bytes.len())?; 47 | 48 | let mut data_offset = offset % Self::SECTOR_SIZE; 49 | let mut aligned_offset = offset - data_offset; 50 | 51 | // Bypass clearing sector buffer for performance reasons 52 | let mut sector_data = MaybeUninit::::uninit(); 53 | let sector_data = unsafe { sector_data.assume_init_mut() }; 54 | 55 | while !bytes.is_empty() { 56 | self.internal_read(aligned_offset, &mut sector_data[..])?; 57 | 58 | let len = bytes.len().min((Self::SECTOR_SIZE - data_offset) as _); 59 | 60 | sector_data[data_offset as usize..][..len].copy_from_slice(&bytes[..len]); 61 | self.internal_erase(aligned_offset / Self::SECTOR_SIZE)?; 62 | self.internal_write(aligned_offset, §or_data[..])?; 63 | 64 | aligned_offset += Self::SECTOR_SIZE; 65 | data_offset = 0; 66 | bytes = &bytes[len..]; 67 | } 68 | 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/stub.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_with_critical_section; 2 | use core::{ptr, slice}; 3 | 4 | #[cfg(not(any(doc, feature = "emulation")))] 5 | compile_error!( 6 | "Select a target using feature: esp32c2, esp32c3, esp32c6, esp32h2, esp32, esp32s2, esp32s3" 7 | ); 8 | 9 | const SUCCESS_CODE: i32 = 0; 10 | const ERROR_CODE: i32 = 1; 11 | const ERASE_BYTE: u8 = 0xff; 12 | const WORD_SIZE: u32 = 4; 13 | const SECTOR_SIZE: u32 = 4 << 10; 14 | const NUM_SECTORS: u32 = 4; 15 | const FLASH_SIZE: u32 = SECTOR_SIZE * NUM_SECTORS; 16 | 17 | static mut FLASH_LOCK: bool = true; 18 | static mut FLASH_DATA: [u8; FLASH_SIZE as usize] = [0u8; FLASH_SIZE as usize]; 19 | 20 | macro_rules! print_error { 21 | ($($tt:tt)*) => { 22 | #[cfg(all(test, feature = "emulation"))] 23 | eprintln!($($tt)*) 24 | }; 25 | } 26 | 27 | fn check( 28 | offset: u32, 29 | length: u32, 30 | data: *const u32, 31 | ) -> bool { 32 | if offset % ALIGN > 0 { 33 | print_error!("Not aligned offset: {offset}"); 34 | return false; 35 | } 36 | if length % ALIGN > 0 { 37 | print_error!("Not aligned length: {length}"); 38 | return false; 39 | } 40 | if offset > SIZE { 41 | print_error!("Offset out of range: {offset} > {SIZE}"); 42 | return false; 43 | } 44 | if offset + length > SIZE { 45 | print_error!("Length out of range: {offset} + {length} > {SIZE}"); 46 | return false; 47 | } 48 | if length > MAX_LEN { 49 | print_error!("Length out of range: {length} > {MAX_LEN}"); 50 | return false; 51 | } 52 | let addr = unsafe { (data as *const u8).offset_from(ptr::null()) } as u32; 53 | if addr % ALIGN > 0 { 54 | print_error!("Not aligned data: {addr:#0x}"); 55 | return false; 56 | } 57 | if unsafe { FLASH_LOCK } { 58 | print_error!("Flash locked"); 59 | return false; 60 | } 61 | true 62 | } 63 | 64 | pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *mut u32, len: u32) -> i32 { 65 | if check::(src_addr, len, data) { 66 | maybe_with_critical_section(|| { 67 | let src = unsafe { slice::from_raw_parts_mut(data as *mut u8, len as _) }; 68 | unsafe { src.copy_from_slice(&FLASH_DATA[src_addr as usize..][..len as usize]) }; 69 | }); 70 | SUCCESS_CODE 71 | } else { 72 | ERROR_CODE 73 | } 74 | } 75 | 76 | pub(crate) fn esp_rom_spiflash_unlock() -> i32 { 77 | maybe_with_critical_section(|| { 78 | unsafe { FLASH_LOCK = false }; 79 | }); 80 | SUCCESS_CODE 81 | } 82 | 83 | pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { 84 | if check::<1, NUM_SECTORS, 1>(sector_number, 1, ptr::null()) { 85 | maybe_with_critical_section(|| { 86 | let dst_addr = sector_number * SECTOR_SIZE; 87 | let len = SECTOR_SIZE; 88 | unsafe { FLASH_DATA[dst_addr as usize..][..len as usize].fill(ERASE_BYTE) }; 89 | }); 90 | SUCCESS_CODE 91 | } else { 92 | ERROR_CODE 93 | } 94 | } 95 | 96 | pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { 97 | if check::(dest_addr, len, data) { 98 | maybe_with_critical_section(|| { 99 | let dst = unsafe { slice::from_raw_parts(data as *const u8, len as _) }; 100 | for (d, s) in unsafe { &mut FLASH_DATA[dest_addr as usize..][..len as usize] } 101 | .iter_mut() 102 | .zip(dst) 103 | { 104 | *d &= *s; 105 | } 106 | }); 107 | SUCCESS_CODE 108 | } else { 109 | ERROR_CODE 110 | } 111 | } 112 | --------------------------------------------------------------------------------