├── .github └── workflows │ └── issue_handler.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── core-isa-parser ├── Cargo.toml └── src │ └── lib.rs ├── exception-esp32.x.jinja ├── exception-esp8266.x ├── interrupt_level_masks.rs.jinja ├── procmacros ├── Cargo.toml └── src │ └── lib.rs ├── rust-toolchain.toml ├── src ├── exception.rs ├── exception │ ├── assembly_esp32.rs │ ├── assembly_esp8266.rs │ ├── esp32.rs │ └── esp8266.rs ├── interrupt.rs └── lib.rs └── xtensa.in.x /.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 | 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "core-isa-parser/xtensa-overlays"] 2 | path = core-isa-parser/xtensa-overlays 3 | url = https://github.com/espressif/xtensa-overlays.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtensa-lx-rt" 3 | version = "0.16.0" 4 | authors = [ 5 | "Scott Mabin ", 6 | "Arjan Mels ", 7 | "Robin Appelman ", 8 | ] 9 | edition = "2021" 10 | rust-version = "1.65" 11 | description = "Low level access for Xtensa LX processors" 12 | readme = "README.md" 13 | repository = "https://github.com/esp-rs/xtensa-lx-rt" 14 | license = "MIT OR Apache-2.0" 15 | keywords = ["xtensa", "lx", "register", "peripheral"] 16 | categories = ["embedded", "hardware-support", "no-std"] 17 | 18 | [package.metadata.docs.rs] 19 | features = ["esp32"] 20 | 21 | [dependencies] 22 | bare-metal = "1.0.0" 23 | r0 = "1.0.0" 24 | xtensa-lx-rt-proc-macros = { path = "procmacros", version = "=0.2.1" } 25 | 26 | [build-dependencies] 27 | core-isa-parser = { path = "core-isa-parser", version = "=0.2.0" } 28 | minijinja = "1.0.7" 29 | 30 | [features] 31 | esp32 = [] 32 | esp32s2 = [] 33 | esp32s3 = [] 34 | esp8266 = [] 35 | -------------------------------------------------------------------------------- /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 2019-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 2019-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 | # `xtensa-lx-rt` 2 | 3 | ⚠️ **NOTE** Development of this crate is now being continued in [xtensa-lx](https://github.com/esp-rs/xtensa-lx). 4 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::env; 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::path::PathBuf; 6 | 7 | use core_isa_parser::{get_config, Chip, Value}; 8 | use minijinja::{context, Environment}; 9 | 10 | fn main() { 11 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 12 | 13 | // Put the linker script somewhere the linker can find it 14 | File::create(out.join("link.x")) 15 | .unwrap() 16 | .write_all(include_bytes!("xtensa.in.x")) 17 | .unwrap(); 18 | 19 | match ( 20 | cfg!(feature = "esp32") || cfg!(feature = "esp32s2") || cfg!(feature = "esp32s3"), 21 | cfg!(feature = "esp8266"), 22 | ) { 23 | (true, false) => handle_esp32(), 24 | (false, true) => handle_esp8266(), 25 | _ => panic!("Either the esp32, esp32s2, esp32s3 or esp8266 feature must be enabled"), 26 | }; 27 | 28 | println!("cargo:rustc-link-search={}", out.display()); 29 | 30 | // Only re-run the build script when xtensa.in.x is changed, 31 | // instead of when any part of the source code changes. 32 | println!("cargo:rerun-if-changed=xtensa.in.x"); 33 | } 34 | 35 | fn handle_esp8266() { 36 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 37 | let exception_source = &include_bytes!("exception-esp8266.x")[..]; 38 | 39 | File::create(out.join("exception.x")) 40 | .unwrap() 41 | .write_all(exception_source) 42 | .unwrap(); 43 | } 44 | 45 | fn handle_esp32() { 46 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 47 | 48 | let rustflags = env::var_os("CARGO_ENCODED_RUSTFLAGS") 49 | .unwrap() 50 | .into_string() 51 | .unwrap(); 52 | 53 | let mut features_to_disable: HashSet = HashSet::new(); 54 | 55 | // Users can pass -Ctarget-feature to the compiler multiple times, so we have to handle that 56 | let target_flags = rustflags 57 | .split(0x1f as char) 58 | .filter(|s| s.starts_with("target-feature=")) 59 | .map(|s| s.strip_prefix("target-feature=")) 60 | .flatten(); 61 | for tf in target_flags { 62 | tf.split(",") 63 | .map(|s| s.trim()) 64 | .filter(|s| s.starts_with('-')) 65 | .map(|s| s.strip_prefix('-')) 66 | .flatten() 67 | .map(rustc_feature_to_xchal_have) 68 | .flatten() 69 | .for_each(|s| { 70 | features_to_disable.insert(s.to_owned()); 71 | }) 72 | } 73 | 74 | let chip = match ( 75 | cfg!(feature = "esp32"), 76 | cfg!(feature = "esp32s2"), 77 | cfg!(feature = "esp32s3"), 78 | ) { 79 | (true, false, false) => Chip::Esp32, 80 | (false, true, false) => Chip::Esp32s2, 81 | (false, false, true) => Chip::Esp32s3, 82 | _ => panic!("Either the esp32, esp32s2, esp32s3 or esp8266 feature must be enabled"), 83 | }; 84 | let isa_config = get_config(chip).expect("Unable to parse ISA config"); 85 | 86 | inject_cfgs(&isa_config, &features_to_disable); 87 | inject_cpu_cfgs(&isa_config); 88 | generate_exception_x(&out, &isa_config); 89 | generate_interrupt_level_masks(&out, &isa_config); 90 | } 91 | 92 | fn generate_interrupt_level_masks(out: &PathBuf, isa_config: &HashMap) { 93 | let mut env = Environment::new(); 94 | let exception_source_template = &include_str!("interrupt_level_masks.rs.jinja")[..]; 95 | env.add_template("interrupt_level_masks.rs", exception_source_template) 96 | .unwrap(); 97 | let template = env.get_template("interrupt_level_masks.rs").unwrap(); 98 | let exception_source = template 99 | .render(context! { 100 | XCHAL_INTLEVEL1_MASK => isa_config.get("XCHAL_INTLEVEL1_MASK").unwrap().as_integer(), 101 | XCHAL_INTLEVEL2_MASK => isa_config.get("XCHAL_INTLEVEL2_MASK").unwrap().as_integer(), 102 | XCHAL_INTLEVEL3_MASK => isa_config.get("XCHAL_INTLEVEL3_MASK").unwrap().as_integer(), 103 | XCHAL_INTLEVEL4_MASK => isa_config.get("XCHAL_INTLEVEL4_MASK").unwrap().as_integer(), 104 | XCHAL_INTLEVEL5_MASK => isa_config.get("XCHAL_INTLEVEL5_MASK").unwrap().as_integer(), 105 | XCHAL_INTLEVEL6_MASK => isa_config.get("XCHAL_INTLEVEL6_MASK").unwrap().as_integer(), 106 | XCHAL_INTLEVEL7_MASK => isa_config.get("XCHAL_INTLEVEL7_MASK").unwrap().as_integer(), 107 | }) 108 | .unwrap(); 109 | File::create(out.join("interrupt_level_masks.rs")) 110 | .unwrap() 111 | .write_all(exception_source.as_bytes()) 112 | .unwrap(); 113 | } 114 | 115 | fn generate_exception_x(out: &PathBuf, isa_config: &HashMap) { 116 | let mut env = Environment::new(); 117 | let exception_source_template = &include_str!("exception-esp32.x.jinja")[..]; 118 | env.add_template("exception.x", exception_source_template) 119 | .unwrap(); 120 | let template = env.get_template("exception.x").unwrap(); 121 | let exception_source = template.render( 122 | context! { 123 | XCHAL_WINDOW_OF4_VECOFS => isa_config.get("XCHAL_WINDOW_OF4_VECOFS").unwrap().as_integer(), 124 | XCHAL_WINDOW_UF4_VECOFS => isa_config.get("XCHAL_WINDOW_UF4_VECOFS").unwrap().as_integer(), 125 | XCHAL_WINDOW_OF8_VECOFS => isa_config.get("XCHAL_WINDOW_OF8_VECOFS").unwrap().as_integer(), 126 | XCHAL_WINDOW_UF8_VECOFS => isa_config.get("XCHAL_WINDOW_UF8_VECOFS").unwrap().as_integer(), 127 | XCHAL_WINDOW_OF12_VECOFS => isa_config.get("XCHAL_WINDOW_OF12_VECOFS").unwrap().as_integer(), 128 | XCHAL_WINDOW_UF12_VECOFS => isa_config.get("XCHAL_WINDOW_UF12_VECOFS").unwrap().as_integer(), 129 | XCHAL_INTLEVEL2_VECOFS => isa_config.get("XCHAL_INTLEVEL2_VECOFS").unwrap().as_integer(), 130 | XCHAL_INTLEVEL3_VECOFS => isa_config.get("XCHAL_INTLEVEL3_VECOFS").unwrap().as_integer(), 131 | XCHAL_INTLEVEL4_VECOFS => isa_config.get("XCHAL_INTLEVEL4_VECOFS").unwrap().as_integer(), 132 | XCHAL_INTLEVEL5_VECOFS => isa_config.get("XCHAL_INTLEVEL5_VECOFS").unwrap().as_integer(), 133 | XCHAL_INTLEVEL6_VECOFS => isa_config.get("XCHAL_INTLEVEL6_VECOFS").unwrap().as_integer(), 134 | XCHAL_NMI_VECOFS => isa_config.get("XCHAL_NMI_VECOFS").unwrap().as_integer(), 135 | XCHAL_KERNEL_VECOFS => isa_config.get("XCHAL_KERNEL_VECOFS").unwrap().as_integer(), 136 | XCHAL_USER_VECOFS => isa_config.get("XCHAL_USER_VECOFS").unwrap().as_integer(), 137 | XCHAL_DOUBLEEXC_VECOFS => isa_config.get("XCHAL_DOUBLEEXC_VECOFS").unwrap().as_integer(), 138 | } 139 | ).unwrap(); 140 | File::create(out.join("exception.x")) 141 | .unwrap() 142 | .write_all(exception_source.as_bytes()) 143 | .unwrap(); 144 | } 145 | 146 | fn inject_cfgs(isa_config: &HashMap, disabled_features: &HashSet) { 147 | for (key, value) in isa_config { 148 | if key.starts_with("XCHAL_HAVE") && *value.as_integer().unwrap_or(&0) != 0 { 149 | if !disabled_features.contains(key) { 150 | println!("cargo:rustc-cfg={}", key); 151 | } 152 | } 153 | } 154 | } 155 | 156 | fn inject_cpu_cfgs(isa_config: &HashMap) { 157 | for (key, value) in isa_config { 158 | if key.starts_with("XCHAL_TIMER") 159 | || key.starts_with("XCHAL_PROFILING") 160 | || key.starts_with("XCHAL_NMI") 161 | { 162 | if let Some(_) = value.as_integer() { 163 | let mut s = String::from(key.trim_end_matches("_INTERRUPT")); 164 | let first = s.chars().position(|c| c == '_').unwrap() + 1; 165 | s.insert_str(first, "HAVE_"); 166 | println!("cargo:rustc-cfg={}", s); 167 | } 168 | } 169 | } 170 | if let Some(value) = isa_config 171 | .get("XCHAL_INTTYPE_MASK_SOFTWARE") 172 | .map(|v| v.as_integer()) 173 | .flatten() 174 | { 175 | for i in 0..value.count_ones() { 176 | println!("cargo:rustc-cfg=XCHAL_HAVE_SOFTWARE{}", i); 177 | } 178 | } 179 | } 180 | 181 | fn rustc_feature_to_xchal_have(s: &str) -> Option<&str> { 182 | // List of rustc features taken from here: 183 | // https://github.com/esp-rs/rust/blob/84ecb3f010525cb1b2e7d4da306099c2eaa3e6cd/compiler/rustc_codegen_ssa/src/target_features.rs#L278 184 | // unlikely to change 185 | Some(match s { 186 | "fp" => "XCHAL_HAVE_FP", 187 | "windowed" => "XCHAL_HAVE_WINDOWED", 188 | "bool" => "XCHAL_HAVE_BOOLEANS", 189 | "loop" => "XCHAL_HAVE_LOOPS", 190 | "sext" => "XCHAL_HAVE_SEXT", 191 | "nsa" => "XCHAL_HAVE_NSA", 192 | "mul32" => "XCHAL_HAVE_MUL32", 193 | "mul32high" => "XCHAL_HAVE_MUL32_HIGH", 194 | "div32" => "XCHAL_HAVE_DIV32", 195 | "mac16" => "XCHAL_HAVE_MAC16", 196 | "dfpaccel" => "XCHAL_HAVE_DFP", 197 | "s32c1i" => "XCHAL_HAVE_S32C1I", 198 | "threadptr" => "XCHAL_HAVE_THREADPTR", 199 | "extendedl32r" => "XCHAL_HAVE_ABSOLUTE_LITERALS", 200 | "debug" => "XCHAL_HAVE_DEBUG", 201 | "exception" => "XCHAL_HAVE_EXCEPTIONS", 202 | "highpriinterrupts" => "XCHAL_HAVE_HIGHPRI_INTERRUPTS", 203 | "coprocessor" => "XCHAL_HAVE_CP", 204 | "interrupt" => "XCHAL_HAVE_INTERRUPTS", 205 | "rvector" => "XCHAL_HAVE_VECTOR_SELECT", 206 | "prid" => "XCHAL_HAVE_PRID", 207 | "regprotect" => "XCHAL_HAVE_MIMIC_CACHEATTR", 208 | "miscsr" => return None, // XCHAL_NUM_MISC_REGS 209 | "timerint" => return None, // XCHAL_NUM_TIMERS 210 | "atomctl" => return None, 211 | "memctl" => return None, 212 | _ => return None, 213 | }) 214 | } 215 | -------------------------------------------------------------------------------- /core-isa-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "core-isa-parser" 3 | version = "0.2.0" 4 | authors = [ 5 | "Jesse Braham ", 6 | "Björn Quentin ", 7 | ] 8 | edition = "2021" 9 | description = "Parse the core-isa.h headers from Espressif's xtensa-overlays repository" 10 | repository = "https://github.com/esp-rs/xtensa-lx-rt" 11 | license = "MIT OR Apache-2.0" 12 | 13 | [dependencies] 14 | anyhow = "1.0" 15 | enum-as-inner = "0.4.0" 16 | regex = "1.5" 17 | strum = "0.24.0" 18 | strum_macros = "0.24.0" 19 | -------------------------------------------------------------------------------- /core-isa-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Parse the core-isa.h headers from Espressif's xtensa-overlays repository. 2 | //! 3 | //! 4 | 5 | use std::{collections::HashMap, env, fs, path::PathBuf, str::FromStr}; 6 | 7 | use anyhow::Result; 8 | use enum_as_inner::EnumAsInner; 9 | use regex::Regex; 10 | use strum_macros::{Display, EnumIter, EnumString}; 11 | 12 | /// The chips which are present in the xtensa-overlays repository 13 | /// 14 | /// When `.to_string()` is called on a variant, the resulting string is the path 15 | /// to the chip's corresponding directory. 16 | #[derive(Debug, Clone, Copy, PartialEq, Display, EnumIter)] 17 | pub enum Chip { 18 | #[strum(to_string = "xtensa_esp32")] 19 | Esp32, 20 | #[strum(to_string = "xtensa_esp32s2")] 21 | Esp32s2, 22 | #[strum(to_string = "xtensa_esp32s3")] 23 | Esp32s3, 24 | #[strum(to_string = "xtensa_lx106")] 25 | Esp8266, 26 | } 27 | 28 | impl Chip { 29 | fn core_isa_path(&self) -> Result { 30 | let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) 31 | .join("xtensa-overlays") 32 | .join(self.to_string()) 33 | .join("newlib/newlib/libc/sys/xtensa/include/xtensa/config/core-isa.h") 34 | .canonicalize()?; 35 | 36 | Ok(path) 37 | } 38 | } 39 | 40 | /// The valid interrupt types declared in the `core-isa.h` headers 41 | #[derive(Debug, Clone, Copy, PartialEq, EnumString)] 42 | pub enum InterruptType { 43 | #[strum(serialize = "XTHAL_INTTYPE_EXTERN_EDGE")] 44 | ExternEdge, 45 | #[strum(serialize = "XTHAL_INTTYPE_EXTERN_LEVEL")] 46 | ExternLevel, 47 | #[strum(serialize = "XTHAL_INTTYPE_NMI")] 48 | Nmi, 49 | #[strum(serialize = "XTHAL_INTTYPE_PROFILING")] 50 | Profiling, 51 | #[strum(serialize = "XTHAL_INTTYPE_SOFTWARE")] 52 | Software, 53 | #[strum(serialize = "XTHAL_INTTYPE_TIMER")] 54 | Timer, 55 | #[strum(serialize = "XTHAL_TIMER_UNCONFIGURED")] 56 | TimerUnconfigured, 57 | } 58 | 59 | /// The allowable value types for definitions 60 | #[derive(Debug, Clone, PartialEq, EnumAsInner)] 61 | pub enum Value { 62 | Integer(i64), 63 | Interrupt(InterruptType), 64 | String(String), 65 | } 66 | 67 | /// Parse the configuration for the given chip 68 | /// 69 | /// Returns a hashmap with the definition identifiers as keys and the 70 | /// corresponding parsed values as values. 71 | pub fn get_config(chip: Chip) -> Result> { 72 | let re_define = Regex::new(r"^#define[\s]+([a-zA-Z\d_]+)[\s]+([^\s]+)")?; 73 | let re_ident = Regex::new(r"^[a-zA-Z\d_]+$")?; 74 | let re_string = Regex::new(r#""([^"]+)""#)?; 75 | 76 | // Iterate through each line containing a definition. Attempt to match the 77 | // various components and map identifiers to values. 78 | let mut map: HashMap = HashMap::new(); 79 | for define in find_all_defines(chip)? { 80 | if !re_define.is_match(&define) { 81 | println!("Define not matched: {}", define); 82 | continue; 83 | } 84 | 85 | let captures = re_define.captures(&define).unwrap(); 86 | let identifier = captures.get(1).unwrap().as_str().to_string(); 87 | let value = captures.get(2).unwrap().as_str().to_string(); 88 | 89 | let value = if let Ok(integer) = value.parse::() { 90 | // Decimal integer literal 91 | Value::Integer(integer) 92 | } else if let Ok(integer) = i64::from_str_radix(&value.replace("0x", ""), 16) { 93 | // Hexadecimal integer literal 94 | Value::Integer(integer) 95 | } else if let Ok(interrupt) = InterruptType::from_str(&value) { 96 | // Interrupt type 97 | Value::Interrupt(interrupt) 98 | } else if re_string.is_match(&value) { 99 | // String 100 | Value::String(value.replace("\"", "")) 101 | } else if re_ident.is_match(&value) && map.contains_key(&value) { 102 | // Identifier 103 | map.get(&value).unwrap().to_owned() 104 | } else { 105 | // We will handle this particular case after, so no need to report it. 106 | if chip != Chip::Esp32 && identifier != "XCHAL_USE_MEMCTL" { 107 | println!("Unable to process definition: {} = {}", identifier, value); 108 | } 109 | continue; 110 | }; 111 | 112 | map.insert(identifier, value); 113 | } 114 | 115 | Ok(map) 116 | } 117 | 118 | fn find_all_defines(chip: Chip) -> Result> { 119 | let path = chip.core_isa_path()?; 120 | let lines = fs::read_to_string(path)? 121 | .lines() 122 | .filter_map(|line| { 123 | if line.starts_with("#define") { 124 | Some(line.to_string()) 125 | } else { 126 | None 127 | } 128 | }) 129 | .collect::>(); 130 | 131 | Ok(lines) 132 | } 133 | -------------------------------------------------------------------------------- /exception-esp32.x.jinja: -------------------------------------------------------------------------------- 1 | /* exception vector for the ESP32, requiring high priority interrupts and register window support */ 2 | 3 | /* high level exception/interrupt routines, which can be override with Rust functions */ 4 | PROVIDE(__exception = __default_exception); 5 | PROVIDE(__user_exception = __default_user_exception); 6 | PROVIDE(__double_exception = __default_double_exception); 7 | PROVIDE(__level_1_interrupt = __default_interrupt); 8 | PROVIDE(__level_2_interrupt = __default_interrupt); 9 | PROVIDE(__level_3_interrupt = __default_interrupt); 10 | PROVIDE(__level_4_interrupt = __default_interrupt); 11 | PROVIDE(__level_5_interrupt = __default_interrupt); 12 | PROVIDE(__level_6_interrupt = __default_interrupt); 13 | PROVIDE(__level_7_interrupt = __default_interrupt); 14 | 15 | /* high level CPU interrupts */ 16 | PROVIDE(Timer0 = __default_user_exception); 17 | PROVIDE(Timer1 = __default_user_exception); 18 | PROVIDE(Timer2 = __default_user_exception); 19 | PROVIDE(Timer3 = __default_user_exception); 20 | PROVIDE(Profiling = __default_user_exception); 21 | PROVIDE(NMI = __default_user_exception); 22 | PROVIDE(Software0 = __default_user_exception); 23 | PROVIDE(Software1 = __default_user_exception); 24 | 25 | /* low level exception/interrupt, which must be overridden using naked functions */ 26 | PROVIDE(__naked_user_exception = __default_naked_exception); 27 | PROVIDE(__naked_kernel_exception = __default_naked_exception); 28 | PROVIDE(__naked_double_exception = __default_naked_double_exception); 29 | PROVIDE(__naked_level_2_interrupt = __default_naked_level_2_interrupt); 30 | PROVIDE(__naked_level_3_interrupt = __default_naked_level_3_interrupt); 31 | PROVIDE(__naked_level_4_interrupt = __default_naked_level_4_interrupt); 32 | PROVIDE(__naked_level_5_interrupt = __default_naked_level_5_interrupt); 33 | PROVIDE(__naked_level_6_interrupt = __default_naked_level_6_interrupt); 34 | PROVIDE(__naked_level_7_interrupt = __default_naked_level_7_interrupt); 35 | 36 | 37 | /* needed to force inclusion of the vectors */ 38 | EXTERN(__default_exception); 39 | EXTERN(__default_double_exception); 40 | EXTERN(__default_interrupt); 41 | 42 | EXTERN(__default_naked_exception); 43 | EXTERN(__default_naked_double_exception); 44 | EXTERN(__default_naked_level_2_interrupt); 45 | EXTERN(__default_naked_level_3_interrupt); 46 | EXTERN(__default_naked_level_4_interrupt); 47 | EXTERN(__default_naked_level_5_interrupt); 48 | EXTERN(__default_naked_level_6_interrupt); 49 | EXTERN(__default_naked_level_7_interrupt); 50 | 51 | 52 | /* Define output sections */ 53 | SECTIONS { 54 | 55 | .vectors : 56 | { 57 | /* 58 | Each vector has 64 bytes that it must fit inside. For each vector we calculate the size of the previous one, 59 | and subtract that from 64 and start the new vector there. 60 | */ 61 | _init_start = ABSOLUTE(.); 62 | . = ALIGN(64); 63 | KEEP(*(.WindowOverflow4.text)); 64 | . = ALIGN(64); 65 | KEEP(*(.WindowUnderflow4.text)); 66 | . = ALIGN(64); 67 | KEEP(*(.WindowOverflow8.text)); 68 | . = ALIGN(64); 69 | KEEP(*(.WindowUnderflow8.text)); 70 | . = ALIGN(64); 71 | KEEP(*(.WindowOverflow12.text)); 72 | . = ALIGN(64); 73 | KEEP(*(.WindowUnderflow12.text)); 74 | . = ALIGN(64); 75 | KEEP(*(.Level2InterruptVector.text)); 76 | . = ALIGN(64); 77 | KEEP(*(.Level3InterruptVector.text)); 78 | . = ALIGN(64); 79 | KEEP(*(.Level4InterruptVector.text)); 80 | . = ALIGN(64); 81 | KEEP(*(.Level5InterruptVector.text)); 82 | . = ALIGN(64); 83 | KEEP(*(.DebugExceptionVector.text)); 84 | . = ALIGN(64); 85 | KEEP(*(.NMIExceptionVector.text)); 86 | . = ALIGN(64); 87 | KEEP(*(.KernelExceptionVector.text)); 88 | . = ALIGN(64); 89 | KEEP(*(.UserExceptionVector.text)); 90 | . = ALIGN(128); 91 | KEEP(*(.DoubleExceptionVector.text)); 92 | . = ALIGN(64); 93 | . = ALIGN(0x400); 94 | _init_end = ABSOLUTE(.); 95 | } > vectors_seg 96 | } 97 | -------------------------------------------------------------------------------- /exception-esp8266.x: -------------------------------------------------------------------------------- 1 | /* exception vector for the lx106, only requiring the basic exception support */ 2 | 3 | /* high level exception/interrupt routines, which can be override with Rust functions */ 4 | PROVIDE(__exception = __default_exception); 5 | PROVIDE(__kernel_exception = __default_exception); 6 | PROVIDE(__double_exception = __default_double_exception); 7 | PROVIDE(__nmi_exception = __default_exception); 8 | PROVIDE(__debug_exception = __default_exception); 9 | PROVIDE(__alloc_exception = __default_exception); 10 | PROVIDE(__level_1_interrupt = __default_interrupt); 11 | 12 | /* low level exception/interrupt, which must be overridden using naked functions */ 13 | PROVIDE(__naked_user_exception = __default_naked_exception); 14 | PROVIDE(__naked_kernel_exception = __default_naked_kernel_exception); 15 | PROVIDE(__naked_double_exception = __default_naked_double_exception); 16 | PROVIDE(__naked_nmi_exception = __default_naked_nmi_exception); 17 | PROVIDE(__naked_debug_exception = __default_naked_debug_exception); 18 | PROVIDE(__naked_alloc_exception = __default_naked_alloc_exception); 19 | 20 | /* needed to force inclusion of the vectors */ 21 | EXTERN(__default_exception); 22 | EXTERN(__default_double_exception); 23 | EXTERN(__default_interrupt); 24 | 25 | EXTERN(__default_naked_exception); 26 | EXTERN(__default_naked_exception); 27 | EXTERN(__default_naked_double_exception); 28 | EXTERN(__default_naked_nmi_exception); 29 | EXTERN(__default_naked_debug_exception); 30 | EXTERN(__default_naked_alloc_exception); 31 | 32 | /* Define output sections */ 33 | SECTIONS { 34 | 35 | .vectors : 36 | { 37 | . = 0x0; 38 | _init_start = ABSOLUTE(.); 39 | . = 0x10; 40 | KEEP(*(.DebugException.text)); 41 | . = 0x20; 42 | KEEP(*(.NMIException.text)); 43 | . = 0x40; 44 | KEEP(*(.KernelException.text)); 45 | . = 0x50; 46 | KEEP(*(.UserException.text)); 47 | . = 0x70; 48 | KEEP(*(.DoubleException.text)); 49 | . = 0x80; 50 | 51 | _init_end = ABSOLUTE(.); 52 | } > vectors_seg 53 | } -------------------------------------------------------------------------------- /interrupt_level_masks.rs.jinja: -------------------------------------------------------------------------------- 1 | pub enum CpuInterruptLevel { 2 | Level1, 3 | Level2, 4 | Level3, 5 | Level4, 6 | Level5, 7 | Level6, 8 | Level7, 9 | } 10 | 11 | impl CpuInterruptLevel { 12 | pub fn mask(&self) -> u32 { 13 | match &self { 14 | CpuInterruptLevel::Level1 => {{ XCHAL_INTLEVEL1_MASK }}u32, 15 | CpuInterruptLevel::Level2 => {{ XCHAL_INTLEVEL2_MASK }}u32, 16 | CpuInterruptLevel::Level3 => {{ XCHAL_INTLEVEL3_MASK }}u32, 17 | CpuInterruptLevel::Level4 => {{ XCHAL_INTLEVEL4_MASK }}u32, 18 | CpuInterruptLevel::Level5 => {{ XCHAL_INTLEVEL5_MASK }}u32, 19 | CpuInterruptLevel::Level6 => {{ XCHAL_INTLEVEL6_MASK }}u32, 20 | CpuInterruptLevel::Level7 => {{ XCHAL_INTLEVEL7_MASK }}u32, 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /procmacros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtensa-lx-rt-proc-macros" 3 | authors = [ 4 | "Jorge Aparicio ", 5 | "Arjan Mels ", 6 | "Scott Mabin ", 7 | ] 8 | version = "0.2.1" 9 | edition = "2021" 10 | rust-version = "1.65" 11 | description = "Attributes re-exported in `xtensa-lx-rt`" 12 | documentation = "https://docs.rs/xtensa-lx-rt" 13 | repository = "https://github.com/esp-rs/xtensa-lx-rt" 14 | license = "MIT OR Apache-2.0" 15 | keywords = ["esp32", "xtensa-lx-rt", "runtime", "startup"] 16 | categories = ["embedded", "no-std"] 17 | 18 | [lib] 19 | proc-macro = true 20 | 21 | [dependencies] 22 | darling = "0.20" 23 | proc-macro2 = "1.0" 24 | quote = "1.0" 25 | syn = { version = "2.0", features = ["extra-traits", "full"] } 26 | -------------------------------------------------------------------------------- /procmacros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Internal implementation details of `xtensa-lx-rt`. 2 | //! 3 | //! Do not use this crate directly. 4 | 5 | #![deny(warnings)] 6 | 7 | extern crate proc_macro; 8 | 9 | use std::collections::HashSet; 10 | 11 | use darling::ast::NestedMeta; 12 | use proc_macro::TokenStream; 13 | use proc_macro2::Span; 14 | use quote::quote; 15 | use syn::{ 16 | parse, 17 | parse_macro_input, 18 | spanned::Spanned, 19 | AttrStyle, 20 | Attribute, 21 | FnArg, 22 | Ident, 23 | Item, 24 | ItemFn, 25 | ItemStatic, 26 | ReturnType, 27 | StaticMutability, 28 | Stmt, 29 | Type, 30 | Visibility, 31 | }; 32 | 33 | /// Marks a function as the main function to be called on program start 34 | #[proc_macro_attribute] 35 | pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { 36 | let mut f = parse_macro_input!(input as ItemFn); 37 | 38 | // check the function signature 39 | let valid_signature = f.sig.constness.is_none() 40 | && f.vis == Visibility::Inherited 41 | && f.sig.abi.is_none() 42 | && f.sig.inputs.is_empty() 43 | && f.sig.generics.params.is_empty() 44 | && f.sig.generics.where_clause.is_none() 45 | && f.sig.variadic.is_none() 46 | && match f.sig.output { 47 | ReturnType::Default => false, 48 | ReturnType::Type(_, ref ty) => match **ty { 49 | Type::Never(_) => true, 50 | _ => false, 51 | }, 52 | }; 53 | 54 | if !valid_signature { 55 | return parse::Error::new( 56 | f.span(), 57 | "`#[entry]` function must have signature `[unsafe] fn() -> !`", 58 | ) 59 | .to_compile_error() 60 | .into(); 61 | } 62 | 63 | if !args.is_empty() { 64 | return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") 65 | .to_compile_error() 66 | .into(); 67 | } 68 | 69 | let (statics, stmts) = match extract_static_muts(f.block.stmts) { 70 | Err(e) => return e.to_compile_error().into(), 71 | Ok(x) => x, 72 | }; 73 | 74 | f.sig.ident = Ident::new( 75 | &format!("__xtensa_lx_rt_{}", f.sig.ident), 76 | Span::call_site(), 77 | ); 78 | f.sig.inputs.extend(statics.iter().map(|statik| { 79 | let ident = &statik.ident; 80 | let ty = &statik.ty; 81 | let attrs = &statik.attrs; 82 | 83 | // Note that we use an explicit `'static` lifetime for the entry point arguments. This makes 84 | // it more flexible, and is sound here, since the entry will not be called again, ever. 85 | syn::parse::( 86 | quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(), 87 | ) 88 | .unwrap() 89 | })); 90 | f.block.stmts = stmts; 91 | 92 | let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); 93 | let ident = &f.sig.ident; 94 | 95 | let resource_args = statics 96 | .iter() 97 | .map(|statik| { 98 | let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone()); 99 | let ident = &statik.ident; 100 | let ty = &statik.ty; 101 | let expr = &statik.expr; 102 | quote! { 103 | #(#cfgs)* 104 | { 105 | #(#attrs)* 106 | static mut #ident: #ty = #expr; 107 | &mut #ident 108 | } 109 | } 110 | }) 111 | .collect::>(); 112 | 113 | if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Entry) { 114 | return error; 115 | } 116 | 117 | let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); 118 | 119 | quote!( 120 | #(#cfgs)* 121 | #(#attrs)* 122 | #[doc(hidden)] 123 | #[export_name = "main"] 124 | pub unsafe extern "C" fn #tramp_ident() { 125 | #ident( 126 | #(#resource_args),* 127 | ) 128 | } 129 | 130 | #[allow(clippy::inline_always)] 131 | #[inline(always)] 132 | #f 133 | ) 134 | .into() 135 | } 136 | 137 | /// Marks a function as the exception handler 138 | #[proc_macro_attribute] 139 | pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { 140 | let mut f = parse_macro_input!(input as ItemFn); 141 | 142 | if !args.is_empty() { 143 | return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") 144 | .to_compile_error() 145 | .into(); 146 | } 147 | 148 | if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) { 149 | return error; 150 | } 151 | 152 | let valid_signature = f.sig.constness.is_none() 153 | && f.vis == Visibility::Inherited 154 | && f.sig.abi.is_none() 155 | && f.sig.inputs.len() <= 2 156 | && f.sig.generics.params.is_empty() 157 | && f.sig.generics.where_clause.is_none() 158 | && f.sig.variadic.is_none() 159 | && match f.sig.output { 160 | ReturnType::Default => true, 161 | ReturnType::Type(_, ref ty) => match **ty { 162 | Type::Tuple(ref tuple) => tuple.elems.is_empty(), 163 | Type::Never(..) => true, 164 | _ => false, 165 | }, 166 | }; 167 | 168 | if !valid_signature { 169 | return parse::Error::new( 170 | f.span(), 171 | "`#[exception]` handlers must have signature `[unsafe] fn([ExceptionCause[, Context]) [-> !]`", 172 | ) 173 | .to_compile_error() 174 | .into(); 175 | } 176 | 177 | let (statics, stmts) = match extract_static_muts(f.block.stmts) { 178 | Err(e) => return e.to_compile_error().into(), 179 | Ok(x) => x, 180 | }; 181 | 182 | f.sig.ident = Ident::new(&format!("__xtensa_lx_6_{}", f.sig.ident), Span::call_site()); 183 | f.sig.inputs.extend(statics.iter().map(|statik| { 184 | let ident = &statik.ident; 185 | let ty = &statik.ty; 186 | let attrs = &statik.attrs; 187 | syn::parse::(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into()) 188 | .unwrap() 189 | })); 190 | f.block.stmts = stmts; 191 | 192 | let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); 193 | 194 | quote!( 195 | #(#cfgs)* 196 | #(#attrs)* 197 | #[doc(hidden)] 198 | #[export_name = "__user_exception"] 199 | #f 200 | ) 201 | .into() 202 | } 203 | 204 | /// Marks a function as the interrupt handler, with optional interrupt level indicated 205 | /// 206 | /// When the function is also marked `#[naked]`, it is a low-level interrupt handler: 207 | /// no entry and exit code to store processor state will be generated. 208 | /// The user needs to ensure that all registers which are used are saved and restored and that 209 | /// the proper return instruction is used. 210 | #[proc_macro_attribute] 211 | pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { 212 | let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); 213 | 214 | let attr_args = match NestedMeta::parse_meta_list(args.into()) { 215 | Ok(v) => v, 216 | Err(e) => { 217 | return TokenStream::from(darling::Error::from(e).write_errors()); 218 | } 219 | }; 220 | 221 | if attr_args.len() > 1 { 222 | return parse::Error::new( 223 | Span::call_site(), 224 | "This attribute accepts zero or 1 arguments", 225 | ) 226 | .to_compile_error() 227 | .into(); 228 | } 229 | 230 | let mut level = 1; 231 | 232 | if attr_args.len() == 1 { 233 | match &attr_args[0] { 234 | NestedMeta::Lit(syn::Lit::Int(lit_int)) => match lit_int.base10_parse::() { 235 | Ok(x) => level = x, 236 | Err(_) => { 237 | return parse::Error::new( 238 | Span::call_site(), 239 | "This attribute accepts an integer attribute", 240 | ) 241 | .to_compile_error() 242 | .into() 243 | } 244 | }, 245 | _ => { 246 | return parse::Error::new( 247 | Span::call_site(), 248 | "This attribute accepts an integer attribute", 249 | ) 250 | .to_compile_error() 251 | .into() 252 | } 253 | } 254 | } 255 | 256 | if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) { 257 | return error; 258 | } 259 | 260 | let naked = f.attrs.iter().position(|x| eq(x, "naked")).is_some(); 261 | 262 | let ident_s = if naked { 263 | format!("__naked_level_{}_interrupt", level) 264 | } else { 265 | format!("__level_{}_interrupt", level) 266 | }; 267 | 268 | if naked && (level < 2 || level > 7) { 269 | return parse::Error::new( 270 | f.span(), 271 | "`#[naked]` `#[interrupt]` handlers must have interrupt level >=2 and <=7", 272 | ) 273 | .to_compile_error() 274 | .into(); 275 | } else if !naked && (level < 1 || level > 7) { 276 | return parse::Error::new( 277 | f.span(), 278 | "`#[interrupt]` handlers must have interrupt level >=1 and <=7", 279 | ) 280 | .to_compile_error() 281 | .into(); 282 | } 283 | 284 | let valid_signature = f.sig.constness.is_none() 285 | && f.vis == Visibility::Inherited 286 | && f.sig.abi.is_none() 287 | && ((!naked && f.sig.inputs.len() <= 2) || (naked && f.sig.inputs.len() == 0)) 288 | && f.sig.generics.params.is_empty() 289 | && f.sig.generics.where_clause.is_none() 290 | && f.sig.variadic.is_none() 291 | && match f.sig.output { 292 | ReturnType::Default => true, 293 | ReturnType::Type(_, ref ty) => match **ty { 294 | Type::Tuple(ref tuple) => tuple.elems.is_empty(), 295 | Type::Never(..) => true, 296 | _ => false, 297 | }, 298 | }; 299 | 300 | if !valid_signature { 301 | if naked { 302 | return parse::Error::new( 303 | f.span(), 304 | "`#[naked]` `#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`", 305 | ) 306 | .to_compile_error() 307 | .into(); 308 | } else { 309 | return parse::Error::new( 310 | f.span(), 311 | "`#[interrupt]` handlers must have signature `[unsafe] fn([u32[, Context]]) [-> !]`", 312 | ) 313 | .to_compile_error() 314 | .into(); 315 | } 316 | } 317 | 318 | let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) { 319 | Err(e) => return e.to_compile_error().into(), 320 | Ok(x) => x, 321 | }; 322 | 323 | let inputs = f.sig.inputs.clone(); 324 | 325 | let args = inputs.iter().map(|arg| match arg { 326 | syn::FnArg::Typed(x) => { 327 | let pat = &*x.pat; 328 | quote!(#pat) 329 | } 330 | _ => quote!(#arg), 331 | }); 332 | 333 | f.sig.ident = Ident::new(&format!("__xtensa_lx_6_{}", f.sig.ident), Span::call_site()); 334 | f.sig.inputs.extend(statics.iter().map(|statik| { 335 | let ident = &statik.ident; 336 | let ty = &statik.ty; 337 | let attrs = &statik.attrs; 338 | syn::parse::(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into()) 339 | .unwrap() 340 | })); 341 | f.block.stmts = stmts; 342 | 343 | let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); 344 | let ident = &f.sig.ident; 345 | 346 | let resource_args = statics 347 | .iter() 348 | .map(|statik| { 349 | let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone()); 350 | let ident = &statik.ident; 351 | let ty = &statik.ty; 352 | let expr = &statik.expr; 353 | quote! { 354 | #(#cfgs)* 355 | { 356 | #(#attrs)* 357 | static mut #ident: #ty = #expr; 358 | &mut #ident 359 | } 360 | } 361 | }) 362 | .collect::>(); 363 | 364 | let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); 365 | 366 | if naked { 367 | quote!( 368 | #(#cfgs)* 369 | #(#attrs)* 370 | #[doc(hidden)] 371 | #[export_name = #ident_s] 372 | pub unsafe extern "C" fn #tramp_ident() { 373 | #ident( 374 | #(#resource_args),* 375 | ) 376 | } 377 | 378 | #[allow(clippy::inline_always)] 379 | #[inline(always)] 380 | #f 381 | ) 382 | .into() 383 | } else { 384 | quote!( 385 | #(#cfgs)* 386 | #(#attrs)* 387 | #[doc(hidden)] 388 | #[export_name = #ident_s] 389 | pub unsafe extern "C" fn #tramp_ident( 390 | level: u32, 391 | frame: xtensa_lx_rt::exception::Context 392 | ) { 393 | #ident(#(#args),* 394 | #(#resource_args),* 395 | ) 396 | } 397 | 398 | #[allow(clippy::inline_always)] 399 | #[inline(always)] 400 | #f 401 | ) 402 | .into() 403 | } 404 | } 405 | 406 | /// Marks a function as the pre_init function. This function is called before main and *before 407 | /// the memory is initialized*. 408 | #[proc_macro_attribute] 409 | pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream { 410 | let f = parse_macro_input!(input as ItemFn); 411 | 412 | // check the function signature 413 | let valid_signature = f.sig.constness.is_none() 414 | && f.vis == Visibility::Inherited 415 | && f.sig.unsafety.is_some() 416 | && f.sig.abi.is_none() 417 | && f.sig.inputs.is_empty() 418 | && f.sig.generics.params.is_empty() 419 | && f.sig.generics.where_clause.is_none() 420 | && f.sig.variadic.is_none() 421 | && match f.sig.output { 422 | ReturnType::Default => true, 423 | ReturnType::Type(_, ref ty) => match **ty { 424 | Type::Tuple(ref tuple) => tuple.elems.is_empty(), 425 | _ => false, 426 | }, 427 | }; 428 | 429 | if !valid_signature { 430 | return parse::Error::new( 431 | f.span(), 432 | "`#[pre_init]` function must have signature `unsafe fn()`", 433 | ) 434 | .to_compile_error() 435 | .into(); 436 | } 437 | 438 | if !args.is_empty() { 439 | return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") 440 | .to_compile_error() 441 | .into(); 442 | } 443 | 444 | if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::PreInit) { 445 | return error; 446 | } 447 | 448 | let attrs = f.attrs; 449 | let ident = f.sig.ident; 450 | let block = f.block; 451 | 452 | quote!( 453 | #[export_name = "__pre_init"] 454 | #[allow(missing_docs)] // we make a private fn public, which can trigger this lint 455 | #(#attrs)* 456 | pub unsafe fn #ident() #block 457 | ) 458 | .into() 459 | } 460 | 461 | /// Extracts `static mut` vars from the beginning of the given statements 462 | fn extract_static_muts( 463 | stmts: impl IntoIterator, 464 | ) -> Result<(Vec, Vec), parse::Error> { 465 | let mut istmts = stmts.into_iter(); 466 | 467 | let mut seen = HashSet::new(); 468 | let mut statics = vec![]; 469 | let mut stmts = vec![]; 470 | while let Some(stmt) = istmts.next() { 471 | match stmt { 472 | Stmt::Item(Item::Static(var)) => match var.mutability { 473 | StaticMutability::Mut(_) => { 474 | if seen.contains(&var.ident) { 475 | return Err(parse::Error::new( 476 | var.ident.span(), 477 | format!("the name `{}` is defined multiple times", var.ident), 478 | )); 479 | } 480 | 481 | seen.insert(var.ident.clone()); 482 | statics.push(var); 483 | } 484 | StaticMutability::None => { 485 | stmts.push(Stmt::Item(Item::Static(var))); 486 | } 487 | _ => unimplemented!(), // `StaticMutability` is `#[non_exhaustive]` 488 | }, 489 | _ => { 490 | stmts.push(stmt); 491 | break; 492 | } 493 | } 494 | } 495 | 496 | stmts.extend(istmts); 497 | 498 | Ok((statics, stmts)) 499 | } 500 | 501 | fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { 502 | let mut cfgs = vec![]; 503 | let mut not_cfgs = vec![]; 504 | 505 | for attr in attrs { 506 | if eq(&attr, "cfg") { 507 | cfgs.push(attr); 508 | } else { 509 | not_cfgs.push(attr); 510 | } 511 | } 512 | 513 | (cfgs, not_cfgs) 514 | } 515 | 516 | enum WhiteListCaller { 517 | Entry, 518 | Exception, 519 | Interrupt, 520 | PreInit, 521 | } 522 | 523 | fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> { 524 | let whitelist = &[ 525 | "doc", 526 | "link_section", 527 | "cfg", 528 | "allow", 529 | "warn", 530 | "deny", 531 | "forbid", 532 | "cold", 533 | "ram", 534 | ]; 535 | 536 | 'o: for attr in attrs { 537 | for val in whitelist { 538 | if eq(&attr, &val) { 539 | continue 'o; 540 | } 541 | } 542 | 543 | let err_str = match caller { 544 | WhiteListCaller::Entry => "this attribute is not allowed on a xtensa-lx-rt entry point", 545 | WhiteListCaller::Exception => { 546 | "this attribute is not allowed on an exception handler controlled by xtensa-lx-rt" 547 | } 548 | WhiteListCaller::Interrupt => { 549 | if eq(&attr, "naked") { 550 | continue 'o; 551 | } 552 | 553 | "this attribute is not allowed on an interrupt handler controlled by xtensa-lx-rt" 554 | } 555 | WhiteListCaller::PreInit => { 556 | "this attribute is not allowed on a pre-init controlled by xtensa-lx-rt" 557 | } 558 | }; 559 | 560 | return Err(parse::Error::new(attr.span(), &err_str) 561 | .to_compile_error() 562 | .into()); 563 | } 564 | 565 | Ok(()) 566 | } 567 | 568 | /// Returns `true` if `attr.path` matches `name` 569 | fn eq(attr: &Attribute, name: &str) -> bool { 570 | attr.style == AttrStyle::Outer && attr.path().is_ident(name) 571 | } 572 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | -------------------------------------------------------------------------------- /src/exception.rs: -------------------------------------------------------------------------------- 1 | //! Exception handling 2 | //! 3 | //! Currently specialized for ESP32 (LX6) configuration: which extra registers to store, 4 | //! how many interrupt levels etc. 5 | //! 6 | //! First level interrupts and exceptions save full processor state to the user stack. 7 | //! This includes the coprocessor registers contrary to the esp-idf where these are lazily saved. 8 | //! (Kernel mode option is currently not used.) 9 | //! 10 | //! WindowUnder/Overflow and AllocA use default Xtensa implementation. 11 | //! 12 | //! LoadStoreError and Unaligned are not (yet) implemented: so all accesses to IRAM must 13 | //! be word sized and aligned. 14 | //! 15 | //! Syscall 0 is not (yet) implemented: it doesn't seem to be used in rust. 16 | //! 17 | //! Double Exceptions can only occur during the early setup of the exception handler. Afterwards 18 | //! PS.EXCM is set to 0 to be able to handle WindowUnderflow/Overflow and recursive exceptions will 19 | //! happen instead. 20 | //! 21 | //! In various places call0 are used as long jump: `j.l` syntax is not supported and `call0` 22 | //! can always be expanded to `mov a0,label; call a0`. Care must be taken since A0 is overwritten. 23 | //! 24 | 25 | #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] 26 | mod assembly_esp32; 27 | #[cfg(feature = "esp8266")] 28 | mod assembly_esp8266; 29 | #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] 30 | mod esp32; 31 | #[cfg(feature = "esp8266")] 32 | mod esp8266; 33 | 34 | /// EXCCAUSE register values 35 | /// 36 | /// General Exception Causes. (Values of EXCCAUSE special register set by general exceptions, 37 | /// which vector to the user, kernel, or double-exception vectors). 38 | /// 39 | #[allow(unused)] 40 | #[derive(Debug)] 41 | #[repr(C)] 42 | pub enum ExceptionCause { 43 | /// Illegal Instruction 44 | Illegal = 0, 45 | /// System Call (Syscall Instruction) 46 | Syscall = 1, 47 | /// Instruction Fetch Error 48 | InstrError = 2, 49 | /// Load Store Error 50 | LoadStoreError = 3, 51 | /// Level 1 Interrupt 52 | LevelOneInterrupt = 4, 53 | /// Stack Extension Assist (movsp Instruction) For Alloca 54 | Alloca = 5, 55 | /// Integer Divide By Zero 56 | DivideByZero = 6, 57 | /// Use Of Failed Speculative Access (Not Implemented) 58 | NextPCValueIllegal = 7, 59 | /// Privileged Instruction 60 | Privileged = 8, 61 | /// Unaligned Load Or Store 62 | Unaligned = 9, 63 | /// Reserved 64 | ExternalRegisterPrivilegeError = 10, 65 | /// Reserved 66 | ExclusiveError = 11, 67 | /// Pif Data Error On Instruction Fetch (Rb-200x And Later) 68 | InstrDataError = 12, 69 | /// Pif Data Error On Load Or Store (Rb-200x And Later) 70 | LoadStoreDataError = 13, 71 | /// Pif Address Error On Instruction Fetch (Rb-200x And Later) 72 | InstrAddrError = 14, 73 | /// Pif Address Error On Load Or Store (Rb-200x And Later) 74 | LoadStoreAddrError = 15, 75 | /// Itlb Miss (No Itlb Entry Matches, Hw Refill Also Missed) 76 | ItlbMiss = 16, 77 | /// Itlb Multihit (Multiple Itlb Entries Match) 78 | ItlbMultiHit = 17, 79 | /// Ring Privilege Violation On Instruction Fetch 80 | InstrRing = 18, 81 | /// Size Restriction On Ifetch (Not Implemented) 82 | Reserved19 = 19, 83 | /// Cache Attribute Does Not Allow Instruction Fetch 84 | InstrProhibited = 20, 85 | /// Reserved 86 | Reserved21 = 21, 87 | /// Reserved 88 | Reserved22 = 22, 89 | /// Reserved 90 | Reserved23 = 23, 91 | /// Dtlb Miss (No Dtlb Entry Matches, Hw Refill Also Missed) 92 | DtlbMiss = 24, 93 | /// Dtlb Multihit (Multiple Dtlb Entries Match) 94 | DtlbMultiHit = 25, 95 | /// Ring Privilege Violation On Load Or Store 96 | LoadStoreRing = 26, 97 | /// Size Restriction On Load/Store (Not Implemented) 98 | Reserved27 = 27, 99 | /// Cache Attribute Does Not Allow Load 100 | LoadProhibited = 28, 101 | /// Cache Attribute Does Not Allow Store 102 | StoreProhibited = 29, 103 | /// Reserved 104 | Reserved30 = 30, 105 | /// Reserved 106 | Reserved31 = 31, 107 | /// Access To Coprocessor 0 When Disabled 108 | Cp0Disabled = 32, 109 | /// Access To Coprocessor 1 When Disabled 110 | Cp1Disabled = 33, 111 | /// Access To Coprocessor 2 When Disabled 112 | Cp2Disabled = 34, 113 | /// Access To Coprocessor 3 When Disabled 114 | Cp3Disabled = 35, 115 | /// Access To Coprocessor 4 When Disabled 116 | Cp4Disabled = 36, 117 | /// Access To Coprocessor 5 When Disabled 118 | Cp5Disabled = 37, 119 | /// Access To Coprocessor 6 When Disabled 120 | Cp6Disabled = 38, 121 | /// Access To Coprocessor 7 When Disabled 122 | Cp7Disabled = 39, 123 | 124 | None = 255, 125 | } 126 | 127 | #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] 128 | pub use esp32::Context; 129 | #[cfg(feature = "esp8266")] 130 | pub use esp8266::Context; 131 | -------------------------------------------------------------------------------- /src/exception/assembly_esp32.rs: -------------------------------------------------------------------------------- 1 | use crate::cfg_asm; 2 | use core::arch::{asm, global_asm}; 3 | 4 | // we could cfg symbols away and reduce frame size depending on features enabled 5 | // i.e the frame size is a fixed size based on all the features right now 6 | // we know at compile time if a target has loops for example, if it doesn't 7 | // we could cut that memory usage. 8 | // However in order to conveniently use `addmi` we need 256-byte alignment anyway 9 | // so wasting a bit more stack space seems to be the better option. 10 | // Additionally there is a chunk of memory reserved for spilled registers. 11 | global_asm!( 12 | " 13 | .set XT_STK_PC, 0 14 | .set XT_STK_PS, 4 15 | .set XT_STK_A0, 8 16 | .equ XT_STK_A1, 12 17 | .set XT_STK_A2, 16 18 | .set XT_STK_A3, 20 19 | .set XT_STK_A4, 24 20 | .set XT_STK_A5, 28 21 | .set XT_STK_A6, 32 22 | .set XT_STK_A7, 36 23 | .set XT_STK_A8, 40 24 | .set XT_STK_A9, 44 25 | .set XT_STK_A10, 48 26 | .set XT_STK_A11, 52 27 | .set XT_STK_A12, 56 28 | .set XT_STK_A13, 60 29 | .set XT_STK_A14, 64 30 | .set XT_STK_A15, 68 31 | .set XT_STK_SAR, 72 32 | .set XT_STK_EXCCAUSE, 76 33 | .set XT_STK_EXCVADDR, 80 34 | .set XT_STK_LBEG, 84 // Registers for Loop Option 35 | .set XT_STK_LEND, 88 36 | .set XT_STK_LCOUNT, 92 37 | .set XT_STK_THREADPTR, 96 // freely usable 32-bit register intended for TLS 38 | .set XT_STK_SCOMPARE1, 100 // Register for s32ci instruction 39 | .set XT_STK_BR, 104 // Register for Boolean Option 40 | .set XT_STK_ACCLO, 108 // Registers for MAC16 option 41 | .set XT_STK_ACCHI, 112 42 | .set XT_STK_M0, 116 43 | .set XT_STK_M1, 120 44 | .set XT_STK_M2, 124 45 | .set XT_STK_M3, 128 46 | .set XT_STK_F64R_LO, 132 // Registers for double support option 47 | .set XT_STK_F64R_HI, 136 48 | .set XT_STK_F64S, 140 49 | .set XT_STK_FCR, 144 // Registers for floating point coprocessor 50 | .set XT_STK_FSR, 148 51 | .set XT_STK_F0, 152 52 | .set XT_STK_F1, 156 53 | .set XT_STK_F2, 160 54 | .set XT_STK_F3, 164 55 | .set XT_STK_F4, 168 56 | .set XT_STK_F5, 172 57 | .set XT_STK_F6, 176 58 | .set XT_STK_F7, 180 59 | .set XT_STK_F8, 184 60 | .set XT_STK_F9, 188 61 | .set XT_STK_F10, 192 62 | .set XT_STK_F11, 196 63 | .set XT_STK_F12, 200 64 | .set XT_STK_F13, 204 65 | .set XT_STK_F14, 208 66 | .set XT_STK_F15, 212 67 | .set XT_STK_TMP, 216 68 | 69 | .set XT_STK_FRMSZ, 256 // needs to be multiple of 16 and enough additional free space 70 | // for the registers spilled to the stack (max 8 registers / 0x20 bytes) 71 | // multiple of 256 allows use of addmi instruction 72 | 73 | 74 | 75 | .set PS_INTLEVEL_EXCM, 3 // interrupt handlers above this level shouldn't be written in high level languages 76 | .set PS_INTLEVEL_MASK, 0x0000000f 77 | .set PS_EXCM, 0x00000010 78 | .set PS_UM, 0x00000020 79 | .set PS_WOE, 0x00040000 80 | " 81 | ); 82 | 83 | /// Save processor state to stack. 84 | /// 85 | /// *Must only be called with call0.* 86 | /// *For spill all window registers to work WOE must be enabled on entry 87 | /// 88 | /// Saves all registers except PC, PS, A0, A1 89 | /// 90 | /// Inputs: 91 | /// A0 is the return address 92 | /// A1 is the stack pointers 93 | /// Exceptions are disabled (PS.EXCM = 1) 94 | /// 95 | /// Output: 96 | /// A0 is the return address 97 | /// A1 is the stack pointer 98 | /// A3, A9 are used as scratch registers 99 | /// EPC1 is changed 100 | #[naked] 101 | #[no_mangle] 102 | #[link_section = ".rwtext"] 103 | unsafe extern "C" fn save_context() { 104 | cfg_asm!( 105 | { 106 | " 107 | s32i a2, sp, +XT_STK_A2 108 | s32i a3, sp, +XT_STK_A3 109 | s32i a4, sp, +XT_STK_A4 110 | s32i a5, sp, +XT_STK_A5 111 | s32i a6, sp, +XT_STK_A6 112 | s32i a7, sp, +XT_STK_A7 113 | s32i a8, sp, +XT_STK_A8 114 | s32i a9, sp, +XT_STK_A9 115 | s32i a10, sp, +XT_STK_A10 116 | s32i a11, sp, +XT_STK_A11 117 | s32i a12, sp, +XT_STK_A12 118 | s32i a13, sp, +XT_STK_A13 119 | s32i a14, sp, +XT_STK_A14 120 | s32i a15, sp, +XT_STK_A15 121 | 122 | rsr a3, SAR 123 | s32i a3, sp, +XT_STK_SAR 124 | ", 125 | #[cfg(XCHAL_HAVE_LOOPS)] 126 | " 127 | // Loop Option 128 | rsr a3, LBEG 129 | s32i a3, sp, +XT_STK_LBEG 130 | rsr a3, LEND 131 | s32i a3, sp, +XT_STK_LEND 132 | rsr a3, LCOUNT 133 | s32i a3, sp, +XT_STK_LCOUNT 134 | ", 135 | #[cfg(XCHAL_HAVE_THREADPTR)] 136 | " 137 | // Thread Pointer Option 138 | rur a3, threadptr 139 | s32i a3, sp, +XT_STK_THREADPTR 140 | ", 141 | #[cfg(XCHAL_HAVE_S32C1I)] 142 | " 143 | // Conditional Store Option 144 | rsr a3, scompare1 145 | s32i a3, sp, +XT_STK_SCOMPARE1 146 | ", 147 | #[cfg(XCHAL_HAVE_BOOLEANS)] 148 | " 149 | // Boolean Option 150 | rsr a3, br 151 | s32i a3, sp, +XT_STK_BR 152 | ", 153 | #[cfg(XCHAL_HAVE_MAC16)] 154 | " 155 | // MAC16 Option 156 | rsr a3, acclo 157 | s32i a3, sp, +XT_STK_ACCLO 158 | rsr a3, acchi 159 | s32i a3, sp, +XT_STK_ACCHI 160 | rsr a3, m0 161 | s32i a3, sp, +XT_STK_M0 162 | rsr a3, m1 163 | s32i a3, sp, +XT_STK_M1 164 | rsr a3, m2 165 | s32i a3, sp, +XT_STK_M2 166 | rsr a3, m3 167 | s32i a3, sp, +XT_STK_M3 168 | ", 169 | #[cfg(XCHAL_HAVE_DFP_ACCEL)] 170 | " 171 | // Double Precision Accelerator Option 172 | rur a3, f64r_lo 173 | s32i a3, sp, +XT_STK_F64R_LO 174 | rur a3, f64r_hi 175 | s32i a3, sp, +XT_STK_F64R_HI 176 | rur a3, f64s 177 | s32i a3, sp, +XT_STK_F64S 178 | ", 179 | #[cfg(XCHAL_HAVE_FP)] 180 | " 181 | // Coprocessor Option 182 | rur a3, fcr 183 | s32i a3, sp, +XT_STK_FCR 184 | rur a3, fsr 185 | s32i a3, sp, +XT_STK_FSR 186 | ssi f0, sp, +XT_STK_F0 187 | ssi f1, sp, +XT_STK_F1 188 | ssi f2, sp, +XT_STK_F2 189 | ssi f3, sp, +XT_STK_F3 190 | ssi f4, sp, +XT_STK_F4 191 | ssi f5, sp, +XT_STK_F5 192 | ssi f6, sp, +XT_STK_F6 193 | ssi f7, sp, +XT_STK_F7 194 | ssi f8, sp, +XT_STK_F8 195 | ssi f9, sp, +XT_STK_F9 196 | ssi f10, sp, +XT_STK_F10 197 | ssi f11, sp, +XT_STK_F11 198 | ssi f12, sp, +XT_STK_F12 199 | ssi f13, sp, +XT_STK_F13 200 | ssi f14, sp, +XT_STK_F14 201 | ssi f15, sp, +XT_STK_F15 202 | ", 203 | #[cfg(XCHAL_HAVE_WINDOWED)] 204 | " 205 | s32i a0, sp, +XT_STK_TMP // keep return address on the stack 206 | 207 | // SPILL_REGISTERS macro requires window overflow exceptions to be enabled, 208 | // i.e. PS.EXCM cleared and PS.WOE set. 209 | // Since we are going to clear PS.EXCM, we also need to increase INTLEVEL 210 | // at least to XCHAL_EXCM_LEVEL. This matches that value of effective INTLEVEL 211 | // at entry (CINTLEVEL=max(PS.INTLEVEL, XCHAL_EXCM_LEVEL) when PS.EXCM is set. 212 | // Since WindowOverflow exceptions will trigger inside SPILL_REGISTERS, 213 | // need to save/restore EPC1 as well. 214 | // Note: even though a4-a15 are saved into the exception frame, we should not 215 | // clobber them until after SPILL_REGISTERS. This is because these registers 216 | // may contain live windows belonging to previous frames in the call stack. 217 | // These frames will be spilled by SPILL_REGISTERS, and if the register was 218 | // used as a temporary by this code, the temporary value would get stored 219 | // onto the stack, instead of the real value. 220 | // 221 | 222 | rsr a2, PS // to be restored after SPILL_REGISTERS 223 | movi a0, PS_INTLEVEL_MASK 224 | and a3, a2, a0 // get the current INTLEVEL 225 | bgeui a3, +PS_INTLEVEL_EXCM, 1f // calculate max(INTLEVEL, XCHAL_EXCM_LEVEL) - 3 = XCHAL_EXCM_LEVEL 226 | movi a3, PS_INTLEVEL_EXCM 227 | 1: 228 | movi a0, PS_WOE // clear EXCM, enable window overflow, set new INTLEVEL 229 | or a3, a3, a0 230 | wsr a3, ps 231 | rsr a0, EPC1 232 | 233 | addmi sp, sp, +XT_STK_FRMSZ // go back to spill register region 234 | SPILL_REGISTERS 235 | addmi sp, sp, -XT_STK_FRMSZ // return the current stack pointer 236 | 237 | wsr a2, PS // restore to the value at entry 238 | rsync 239 | wsr a0, EPC1 240 | 241 | l32i a0, sp, +XT_STK_TMP 242 | ", 243 | " 244 | ret 245 | ", 246 | }, 247 | options(noreturn) 248 | ); 249 | } 250 | 251 | global_asm!( 252 | r#" 253 | // Spills all active windowed registers (i.e. registers not visible as 254 | // A0-A15) to their ABI-defined spill regions on the stack. 255 | // It will spill registers to their reserved locations in previous frames. 256 | // 257 | // Unlike the Xtensa HAL implementation, this code requires that the 258 | // EXCM and WOE bit be enabled in PS, and relies on repeated hardware 259 | // exception handling to do the register spills. The trick is to do a 260 | // noop write to the high registers, which the hardware will trap 261 | // (into an overflow exception) in the case where those registers are 262 | // already used by an existing call frame. Then it rotates the window 263 | // and repeats until all but the A0-A3 registers of the original frame 264 | // are guaranteed to be spilled, eventually rotating back around into 265 | // the original frame. Advantages: 266 | // 267 | // - Vastly smaller code size 268 | // 269 | // - More easily maintained if changes are needed to window over/underflow 270 | // exception handling. 271 | // 272 | // - Requires no scratch registers to do its work, so can be used safely in any 273 | // context. 274 | // 275 | // - If the WOE bit is not enabled (for example, in code written for 276 | // the CALL0 ABI), this becomes a silent noop and operates compatbily. 277 | // 278 | // - Hilariously it's ACTUALLY FASTER than the HAL routine. And not 279 | // just a little bit, it's MUCH faster. With a mostly full register 280 | // file on an LX6 core (ESP-32) I'm measuring 145 cycles to spill 281 | // registers with this vs. 279 (!) to do it with 282 | // xthal_spill_windows(). 283 | 284 | .macro SPILL_REGISTERS 285 | and a12, a12, a12 286 | rotw 3 287 | and a12, a12, a12 288 | rotw 3 289 | and a12, a12, a12 290 | rotw 3 291 | and a12, a12, a12 292 | rotw 3 293 | and a12, a12, a12 294 | rotw 4 295 | .endm 296 | "# 297 | ); 298 | 299 | global_asm!( 300 | r#" 301 | .macro SAVE_CONTEXT level:req 302 | mov a0, a1 // save a1/sp 303 | addmi sp, sp, -XT_STK_FRMSZ // only allow multiple of 256 304 | 305 | s32i a0, sp, +XT_STK_A1 // save interruptee's A1/SP 306 | s32e a0, sp, -12 // for debug backtrace 307 | 308 | .ifc \level,1 309 | rsr a0, PS 310 | s32i a0, sp, +XT_STK_PS // save interruptee's PS 311 | 312 | rsr a0, EXCCAUSE 313 | s32i a0, sp, +XT_STK_EXCCAUSE 314 | rsr a0, EXCVADDR 315 | s32i a0, sp, +XT_STK_EXCVADDR 316 | .else 317 | rsr a0, EPS\level 318 | s32i a0, sp, +XT_STK_PS // save interruptee's PS 319 | .endif 320 | 321 | rsr a0, EPC\level 322 | s32i a0, sp, +XT_STK_PC // save interruptee's PC 323 | s32e a0, sp, -16 // for debug backtrace 324 | 325 | rsr a0, EXCSAVE\level 326 | s32i a0, sp, +XT_STK_A0 // save interruptee's A0 327 | 328 | call0 save_context 329 | 330 | .endm 331 | "# 332 | ); 333 | 334 | #[naked] 335 | #[no_mangle] 336 | #[link_section = ".rwtext"] 337 | unsafe extern "C" fn restore_context() { 338 | cfg_asm!( 339 | { 340 | " 341 | l32i a3, sp, +XT_STK_SAR 342 | wsr a3, SAR 343 | ", 344 | #[cfg(XCHAL_HAVE_LOOPS)] 345 | " 346 | // Loop Option 347 | l32i a3, sp, +XT_STK_LBEG 348 | wsr a3, LBEG 349 | l32i a3, sp, +XT_STK_LEND 350 | wsr a3, LEND 351 | l32i a3, sp, +XT_STK_LCOUNT 352 | wsr a3, LCOUNT 353 | ", 354 | #[cfg(XCHAL_HAVE_THREADPTR)] 355 | " 356 | // Thread Pointer Option 357 | l32i a3, sp, +XT_STK_THREADPTR 358 | wur a3, threadptr 359 | ", 360 | #[cfg(XCHAL_HAVE_S32C1I)] 361 | " 362 | // Conditional Store Option 363 | l32i a3, sp, +XT_STK_SCOMPARE1 364 | wsr a3, scompare1 365 | ", 366 | #[cfg(XCHAL_HAVE_BOOLEANS)] 367 | " 368 | // Boolean Option 369 | l32i a3, sp, +XT_STK_BR 370 | wsr a3, br 371 | ", 372 | #[cfg(XCHAL_HAVE_MAC16)] 373 | " 374 | // MAC16 Option 375 | l32i a3, sp, +XT_STK_ACCLO 376 | wsr a3, acclo 377 | l32i a3, sp, +XT_STK_ACCHI 378 | wsr a3, acchi 379 | l32i a3, sp, +XT_STK_M0 380 | wsr a3, m0 381 | l32i a3, sp, +XT_STK_M1 382 | wsr a3, m1 383 | l32i a3, sp, +XT_STK_M2 384 | wsr a3, m2 385 | l32i a3, sp, +XT_STK_M3 386 | wsr a3, m3 387 | ", 388 | #[cfg(XCHAL_HAVE_DFP_ACCEL)] 389 | " 390 | // Double Precision Accelerator Option 391 | l32i a3, sp, +XT_STK_F64R_LO 392 | wur a3, f64r_lo 393 | l32i a3, sp, +XT_STK_F64R_HI 394 | wur a3, f64r_hi 395 | l32i a3, sp, +XT_STK_F64S 396 | wur a3, f64s 397 | ", 398 | #[cfg(XCHAL_HAVE_FP)] 399 | " 400 | // Coprocessor Option 401 | l32i a3, sp, +XT_STK_FCR 402 | wur a3, fcr 403 | l32i a3, sp, +XT_STK_FSR 404 | wur a3, fsr 405 | lsi f0, sp, +XT_STK_F0 406 | lsi f1, sp, +XT_STK_F1 407 | lsi f2, sp, +XT_STK_F2 408 | lsi f3, sp, +XT_STK_F3 409 | lsi f4, sp, +XT_STK_F4 410 | lsi f5, sp, +XT_STK_F5 411 | lsi f6, sp, +XT_STK_F6 412 | lsi f7, sp, +XT_STK_F7 413 | lsi f8, sp, +XT_STK_F8 414 | lsi f9, sp, +XT_STK_F9 415 | lsi f10, sp, +XT_STK_F10 416 | lsi f11, sp, +XT_STK_F11 417 | lsi f12, sp, +XT_STK_F12 418 | lsi f13, sp, +XT_STK_F13 419 | lsi f14, sp, +XT_STK_F14 420 | lsi f15, sp, +XT_STK_F15 421 | ", 422 | " 423 | // general registers 424 | l32i a2, sp, +XT_STK_A2 425 | l32i a3, sp, +XT_STK_A3 426 | l32i a4, sp, +XT_STK_A4 427 | l32i a5, sp, +XT_STK_A5 428 | l32i a6, sp, +XT_STK_A6 429 | l32i a7, sp, +XT_STK_A7 430 | l32i a8, sp, +XT_STK_A8 431 | l32i a9, sp, +XT_STK_A9 432 | l32i a10, sp, +XT_STK_A10 433 | l32i a11, sp, +XT_STK_A11 434 | l32i a12, sp, +XT_STK_A12 435 | l32i a13, sp, +XT_STK_A13 436 | l32i a14, sp, +XT_STK_A14 437 | l32i a15, sp, +XT_STK_A15 438 | ret 439 | ", 440 | }, options(noreturn)); 441 | } 442 | 443 | global_asm!( 444 | r#" 445 | .macro RESTORE_CONTEXT level:req 446 | 447 | // Restore context and return 448 | call0 restore_context 449 | 450 | .ifc \level,1 451 | l32i a0, sp, +XT_STK_PS // retrieve interruptee's PS 452 | wsr a0, PS 453 | l32i a0, sp, +XT_STK_PC // retrieve interruptee's PC 454 | wsr a0, EPC\level 455 | .else 456 | l32i a0, sp, +XT_STK_PS // retrieve interruptee's PS 457 | wsr a0, EPS\level 458 | l32i a0, sp, +XT_STK_PC // retrieve interruptee's PC 459 | wsr a0, EPC\level 460 | .endif 461 | 462 | l32i a0, sp, +XT_STK_A0 // retrieve interruptee's A0 463 | l32i sp, sp, +XT_STK_A1 // remove exception frame 464 | rsync // ensure PS and EPC written 465 | 466 | .endm 467 | "# 468 | ); 469 | 470 | /// Handle Other Exceptions or Level 1 interrupt by storing full context and then 471 | /// calling regular function 472 | /// 473 | /// # Input: 474 | /// * A0 stored in EXCSAVE1 475 | #[naked] 476 | #[no_mangle] 477 | #[link_section = ".rwtext"] 478 | unsafe extern "C" fn __default_naked_exception() { 479 | asm!( 480 | " 481 | SAVE_CONTEXT 1 482 | 483 | movi a0, (PS_INTLEVEL_EXCM | PS_WOE) 484 | wsr a0, PS 485 | rsync 486 | 487 | l32i a6, sp, +XT_STK_EXCCAUSE // put cause in a6 = a2 in callee 488 | beqi a6, 4, .Level1Interrupt 489 | 490 | mov a7, sp // put address of save frame in a7=a3 in callee 491 | call4 __exception // call handler <= actual call! 492 | 493 | j .RestoreContext 494 | 495 | .Level1Interrupt: 496 | movi a0, (1 | PS_WOE) // set PS.INTLEVEL accordingly 497 | wsr a0, PS 498 | rsync 499 | 500 | movi a6, 1 // put interrupt level in a6 = a2 in callee 501 | mov a7, sp // put address of save frame in a7=a3 in callee 502 | call4 __level_1_interrupt // call handler <= actual call! 503 | 504 | .RestoreContext: 505 | RESTORE_CONTEXT 1 506 | 507 | rfe // PS.EXCM is cleared 508 | ", 509 | options(noreturn) 510 | ) 511 | } 512 | 513 | /// Handle Double Exceptions by storing full context and then calling regular function 514 | /// Double exceptions are not a normal occurrence. They indicate a bug of some kind. 515 | /// 516 | /// # Input: 517 | /// * A0 stored in EXCSAVE1 518 | #[naked] 519 | #[no_mangle] 520 | #[link_section = ".rwtext"] 521 | unsafe extern "C" fn __default_naked_double_exception() { 522 | asm!( 523 | " 524 | mov a0, a1 // save a1/sp 525 | addmi sp, sp, -XT_STK_FRMSZ // only allow multiple of 256 526 | 527 | s32i a0, sp, +XT_STK_A1 // save interruptee's A1/SP 528 | s32e a0, sp, -12 // for debug backtrace 529 | 530 | rsr a0, PS 531 | s32i a0, sp, +XT_STK_PS // save interruptee's PS 532 | 533 | rsr a0, EXCCAUSE 534 | s32i a0, sp, +XT_STK_EXCCAUSE 535 | rsr a0, EXCVADDR 536 | s32i a0, sp, +XT_STK_EXCVADDR 537 | 538 | rsr a0, DEPC 539 | s32i a0, sp, +XT_STK_PC // save interruptee's PC 540 | s32e a0, sp, -16 // for debug backtrace 541 | 542 | rsr a0, EXCSAVE7 // ok to reuse EXCSAVE7 for double exception as long as 543 | // double exception is not in first couple of instructions 544 | // of level 7 handler 545 | s32i a0, sp, +XT_STK_A0 // save interruptee's A0 546 | 547 | call0 save_context 548 | 549 | l32i a6, sp, +XT_STK_EXCCAUSE // put cause in a6 = a2 in callee 550 | mov a7, sp // put address of save frame in a7=a3 in callee 551 | call4 __exception // call handler <= actual call! 552 | 553 | // Restore context and return 554 | call0 restore_context 555 | 556 | l32i a0, sp, +XT_STK_PS // retrieve interruptee's PS 557 | wsr a0, PS 558 | l32i a0, sp, +XT_STK_PC // retrieve interruptee's PC 559 | wsr a0, EPC1 560 | 561 | l32i a0, sp, +XT_STK_A0 // retrieve interruptee's A0 562 | l32i sp, sp, +XT_STK_A1 // remove exception frame 563 | rsync // ensure PS and EPC written 564 | 565 | rfde 566 | ", 567 | options(noreturn) 568 | ) 569 | } 570 | 571 | global_asm!( 572 | r#" 573 | .macro HANDLE_INTERRUPT_LEVEL level 574 | SAVE_CONTEXT \level 575 | 576 | movi a0, (\level | PS_WOE) 577 | wsr a0, PS 578 | rsync 579 | 580 | movi a6, \level // put interrupt level in a6 = a2 in callee 581 | mov a7, sp // put address of save frame in a7=a3 in callee 582 | call4 __level_\level\()_interrupt // call handler <= actual call! 583 | 584 | RESTORE_CONTEXT \level 585 | rfi \level 586 | 587 | .endm 588 | "# 589 | ); 590 | 591 | /// Handle Level 2 Interrupt by storing full context and then calling regular function 592 | /// 593 | /// # Input: 594 | /// * A0 stored in EXCSAVE2 595 | #[naked] 596 | #[no_mangle] 597 | #[link_section = ".rwtext"] 598 | unsafe extern "C" fn __default_naked_level_2_interrupt() { 599 | asm!("HANDLE_INTERRUPT_LEVEL 2", options(noreturn)); 600 | } 601 | 602 | /// Handle Level 3 Interrupt by storing full context and then calling regular function 603 | /// 604 | /// # Input: 605 | /// * A0 stored in EXCSAVE3 606 | #[naked] 607 | #[no_mangle] 608 | #[link_section = ".rwtext"] 609 | unsafe extern "C" fn __default_naked_level_3_interrupt() { 610 | asm!("HANDLE_INTERRUPT_LEVEL 3", options(noreturn)); 611 | } 612 | 613 | /// Handle Level 4 Interrupt by storing full context and then calling regular function 614 | /// 615 | /// # Input: 616 | /// * A0 stored in EXCSAVE4 617 | #[naked] 618 | #[no_mangle] 619 | #[link_section = ".rwtext"] 620 | unsafe extern "C" fn __default_naked_level_4_interrupt() { 621 | asm!("HANDLE_INTERRUPT_LEVEL 4", options(noreturn)); 622 | } 623 | 624 | /// Handle Level 5 Interrupt by storing full context and then calling regular function 625 | /// 626 | /// # Input: 627 | /// * A0 stored in EXCSAVE5 628 | #[naked] 629 | #[no_mangle] 630 | #[link_section = ".rwtext"] 631 | unsafe extern "C" fn __default_naked_level_5_interrupt() { 632 | asm!("HANDLE_INTERRUPT_LEVEL 5", options(noreturn)); 633 | } 634 | 635 | /// Handle Level 6 (=Debug) Interrupt by storing full context and then calling regular function 636 | /// 637 | /// # Input: 638 | /// * A0 stored in EXCSAVE6 639 | #[naked] 640 | #[no_mangle] 641 | #[link_section = ".rwtext"] 642 | unsafe extern "C" fn __default_naked_level_6_interrupt() { 643 | asm!("HANDLE_INTERRUPT_LEVEL 6", options(noreturn)); 644 | } 645 | 646 | /// Handle Level 7 (=NMI) Interrupt by storing full context and then calling regular function 647 | /// 648 | /// # Input: 649 | /// * A0 stored in EXCSAVE7 650 | #[naked] 651 | #[no_mangle] 652 | #[link_section = ".rwtext"] 653 | unsafe extern "C" fn __default_naked_level_7_interrupt() { 654 | asm!("HANDLE_INTERRUPT_LEVEL 7", options(noreturn)); 655 | } 656 | -------------------------------------------------------------------------------- /src/exception/assembly_esp8266.rs: -------------------------------------------------------------------------------- 1 | use core::arch::{asm, global_asm}; 2 | 3 | global_asm!( 4 | " 5 | .set XT_STK_PC, 0 6 | .set XT_STK_PS, 4 7 | .set XT_STK_A0, 8 8 | .equ XT_STK_A1, 12 9 | .set XT_STK_A2, 16 10 | .set XT_STK_A3, 20 11 | .set XT_STK_A4, 24 12 | .set XT_STK_A5, 28 13 | .set XT_STK_A6, 32 14 | .set XT_STK_A7, 36 15 | .set XT_STK_A8, 40 16 | .set XT_STK_A9, 44 17 | .set XT_STK_A10, 48 18 | .set XT_STK_A11, 52 19 | .set XT_STK_A12, 56 20 | .set XT_STK_A13, 60 21 | .set XT_STK_A14, 64 22 | .set XT_STK_A15, 68 23 | .set XT_STK_SAR, 72 24 | .set XT_STK_EXCCAUSE, 76 25 | .set XT_STK_EXCVADDR, 80 26 | 27 | .set XT_STK_BASESAVE, 240 28 | .set XT_STK_FRMSZ, 256 // needs to be multiple of 16 and at least 16 free 29 | // (for base save region) 30 | // multiple of 256 allows use of addmi instruction 31 | 32 | .set PS_INTLEVEL_EXCM, 3 33 | .set PS_INTLEVEL_MASK, 0x0000000f 34 | .set PS_EXCM, 0x00000010 35 | .set PS_UM, 0x00000020 36 | .set PS_WOE, 0x00040000 37 | " 38 | ); 39 | 40 | /// Save processor state to stack. 41 | /// 42 | /// *Must only be called with call0.* 43 | /// 44 | /// Saves all registers except PC, PS, A0, A1 45 | /// 46 | /// Inputs: 47 | /// A0 is the return address 48 | /// A1 is the stack pointers 49 | /// Exceptions are disabled (PS.EXCM = 1) 50 | /// 51 | /// Output: 52 | /// A0 is the return address 53 | /// A1 is the stack pointer 54 | /// A3, A9 are used as scratch registers 55 | /// EPC1 is changed 56 | #[naked] 57 | #[no_mangle] 58 | #[link_section = ".rwtext"] 59 | unsafe extern "C" fn save_context() { 60 | asm!( 61 | " 62 | s32i a2, sp, +XT_STK_A2 63 | s32i a3, sp, +XT_STK_A3 64 | s32i a4, sp, +XT_STK_A4 65 | s32i a5, sp, +XT_STK_A5 66 | s32i a6, sp, +XT_STK_A6 67 | s32i a7, sp, +XT_STK_A7 68 | s32i a8, sp, +XT_STK_A8 69 | s32i a9, sp, +XT_STK_A9 70 | s32i a10, sp, +XT_STK_A10 71 | s32i a11, sp, +XT_STK_A11 72 | s32i a12, sp, +XT_STK_A12 73 | s32i a13, sp, +XT_STK_A13 74 | s32i a14, sp, +XT_STK_A14 75 | s32i a15, sp, +XT_STK_A15 76 | 77 | rsr a3, SAR 78 | s32i a3, sp, +XT_STK_SAR 79 | 80 | ret 81 | ", 82 | options(noreturn) 83 | ) 84 | } 85 | 86 | global_asm!( 87 | r#" 88 | .macro SAVE_CONTEXT level:req 89 | 90 | mov a0, a1 // save a1/sp 91 | addmi sp, sp, -XT_STK_FRMSZ // bumb stack pointer 92 | s32i a0, sp, +XT_STK_A1 // save interruptee's A1/SP 93 | 94 | .ifc \level,double 95 | rsr a0, DEPC 96 | .else 97 | rsr a0, EPC\level 98 | .endif 99 | s32i a0, sp, +XT_STK_PC // save interruptee's PC 100 | 101 | .ifc \level,double 102 | rsr a0, EXCSAVE2 // ok to reuse EXCSAVE7 for double exception as long as 103 | // double exception is not in first couple of instructions 104 | // of level 7 handler 105 | .else 106 | rsr a0, EXCSAVE\level 107 | .endif 108 | s32i a0, sp, +XT_STK_A0 // save interruptee's A0 109 | 110 | .ifc \level,1 111 | rsr a0, PS 112 | s32i a0, sp, +XT_STK_PS // save interruptee's PS 113 | 114 | rsr a0, EXCCAUSE 115 | s32i a0, sp, +XT_STK_EXCCAUSE 116 | rsr a0, EXCVADDR 117 | s32i a0, sp, +XT_STK_EXCVADDR 118 | .endif 119 | 120 | .ifc \level,double 121 | rsr a0, EXCCAUSE 122 | s32i a0, sp, +XT_STK_EXCCAUSE 123 | rsr a0, EXCVADDR 124 | s32i a0, sp, +XT_STK_EXCVADDR 125 | .endif 126 | 127 | call0 save_context 128 | 129 | .endm 130 | "# 131 | ); 132 | 133 | #[naked] 134 | #[no_mangle] 135 | #[link_section = ".rwtext"] 136 | unsafe extern "C" fn restore_context() { 137 | asm!( 138 | " 139 | l32i a3, sp, +XT_STK_SAR 140 | wsr a3, SAR 141 | 142 | // general registers 143 | l32i a2, sp, +XT_STK_A2 144 | l32i a3, sp, +XT_STK_A3 145 | l32i a4, sp, +XT_STK_A4 146 | l32i a5, sp, +XT_STK_A5 147 | l32i a6, sp, +XT_STK_A6 148 | l32i a7, sp, +XT_STK_A7 149 | l32i a8, sp, +XT_STK_A8 150 | l32i a9, sp, +XT_STK_A9 151 | l32i a10, sp, +XT_STK_A10 152 | l32i a11, sp, +XT_STK_A11 153 | l32i a12, sp, +XT_STK_A12 154 | l32i a13, sp, +XT_STK_A13 155 | l32i a14, sp, +XT_STK_A14 156 | l32i a15, sp, +XT_STK_A15 157 | 158 | ret 159 | ", 160 | options(noreturn) 161 | ) 162 | } 163 | 164 | global_asm!( 165 | r#" 166 | .macro RESTORE_CONTEXT level:req 167 | 168 | // Restore context and return 169 | call0 restore_context 170 | 171 | .ifc \level,1 172 | l32i a0, sp, +XT_STK_PS // retrieve interruptee's PS 173 | wsr a0, PS 174 | l32i a0, sp, +XT_STK_PC // retrieve interruptee's PC 175 | wsr a0, EPC\level 176 | .endif 177 | 178 | l32i a0, sp, +XT_STK_A0 // retrieve interruptee's A0 179 | l32i sp, sp, +XT_STK_A1 // remove exception frame 180 | rsync // ensure PS and EPC written 181 | 182 | .endm 183 | "# 184 | ); 185 | 186 | /// Handle Other Exceptions or Level 1 interrupt by storing full context and then 187 | /// calling regular function 188 | /// 189 | /// # Input: 190 | /// * A0 stored in EXCSAVE1 191 | #[naked] 192 | #[no_mangle] 193 | #[link_section = ".rwtext"] 194 | unsafe extern "C" fn __default_naked_exception() { 195 | asm!( 196 | " 197 | SAVE_CONTEXT 1 198 | 199 | rsr.EXCCAUSE a2 // put cause in a2 200 | beqi a2, 4, .Level1Interrupt // cause 4 is interrupt 201 | 202 | mov a3, sp // put address of save frame in a3 203 | call0 __exception // call handler <= actual call! 204 | 205 | j .RestoreContext 206 | 207 | .Level1Interrupt: 208 | movi a2, 1 // put interrupt level in a2 in callee 209 | mov a3, sp // put address of save frame in a3 210 | call0 __level_1_interrupt // call handler <= actual call! 211 | 212 | .RestoreContext: 213 | RESTORE_CONTEXT 1 214 | 215 | .byte 0x00, 0x30, 0x00 // rfe 216 | // TODO: 20200509, not supported in llvm yet 217 | ", 218 | options(noreturn) 219 | ) 220 | } 221 | 222 | /// Handle Double Exceptions by storing full context and then calling regular function 223 | /// 224 | /// # Input: 225 | /// * A0 stored in ??? 226 | #[naked] 227 | #[no_mangle] 228 | #[link_section = ".rwtext"] 229 | unsafe extern "C" fn __default_naked_double_exception() { 230 | asm!( 231 | " 232 | SAVE_CONTEXT double 233 | 234 | l32i a2, sp, +XT_STK_EXCCAUSE // put cause in a2 235 | mov a3, sp // put address of save frame in a3 236 | call0 __double_exception // call handler <= actual call! 237 | 238 | RESTORE_CONTEXT double 239 | 240 | .byte 0x00, 0x30, 0x00 // rfe 241 | ", 242 | options(noreturn) 243 | ) 244 | } 245 | 246 | /// Handle Kernel Exceptions by storing full context and then calling regular function 247 | /// 248 | /// # Input: 249 | /// * A0 stored in EXCSAVE1 250 | #[naked] 251 | #[no_mangle] 252 | #[link_section = ".rwtext"] 253 | unsafe extern "C" fn __default_naked_kernel_exception() { 254 | asm!( 255 | " 256 | SAVE_CONTEXT 1 257 | 258 | l32i a2, sp, +XT_STK_EXCCAUSE // put cause in a2 259 | 260 | mov a3, sp // put address of save frame in a3 261 | call0 __kernel_exception // call handler <= actual call! 262 | 263 | RESTORE_CONTEXT 1 264 | 265 | .byte 0x00, 0x30, 0x00 // rfe // PS.EXCM is cleared 266 | // TODO: 20200509, not supported in llvm yet 267 | ", 268 | options(noreturn) 269 | ) 270 | } 271 | 272 | /// Handle NMI Exceptions by storing full context and then calling regular function 273 | /// 274 | /// # Input: 275 | /// * A0 stored in EXCSAVE1 276 | #[naked] 277 | #[no_mangle] 278 | #[link_section = ".rwtext"] 279 | unsafe extern "C" fn __default_naked_nmi_exception() { 280 | asm!( 281 | " 282 | SAVE_CONTEXT 1 283 | 284 | l32i a2, sp, +XT_STK_EXCCAUSE // put cause in a2 285 | 286 | mov a3, sp // put address of save frame in a3 287 | call0 __nmi_exception // call handler <= actual call! 288 | 289 | RESTORE_CONTEXT 1 290 | 291 | .byte 0x00, 0x30, 0x00 // rfe 292 | ", 293 | options(noreturn) 294 | ) 295 | } 296 | 297 | /// Handle Debug Exceptions by storing full context and then calling regular function 298 | /// 299 | /// # Input: 300 | /// * A0 stored in EXCSAVE1 301 | #[naked] 302 | #[no_mangle] 303 | #[link_section = ".rwtext"] 304 | unsafe extern "C" fn __default_naked_debug_exception() { 305 | asm!( 306 | " 307 | SAVE_CONTEXT 1 308 | 309 | l32i a2, sp, +XT_STK_EXCCAUSE // put cause in a2 310 | 311 | mov a3, sp // put address of save frame in a3 312 | call0 __debug_exception // call handler <= actual call! 313 | 314 | RESTORE_CONTEXT 1 315 | 316 | .byte 0x00, 0x30, 0x00 // rfe 317 | ", 318 | options(noreturn) 319 | ) 320 | } 321 | 322 | /// Handle Alloc Exceptions by storing full context and then calling regular function 323 | /// 324 | /// # Input: 325 | /// * A0 stored in EXCSAVE1 326 | #[naked] 327 | #[no_mangle] 328 | #[link_section = ".rwtext"] 329 | unsafe extern "C" fn __default_naked_alloc_exception() { 330 | asm!( 331 | " 332 | SAVE_CONTEXT 1 333 | 334 | l32i a2, sp, +XT_STK_EXCCAUSE // put cause in a2 335 | 336 | mov a3, sp // put address of save frame in a3 337 | call0 __alloc_exception // call handler <= actual call! 338 | 339 | RESTORE_CONTEXT 1 340 | 341 | .byte 0x00, 0x30, 0x00 // rfe 342 | ", 343 | options(noreturn) 344 | ) 345 | } 346 | -------------------------------------------------------------------------------- /src/exception/esp32.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | use super::ExceptionCause; 4 | 5 | /// State of the CPU saved when entering exception or interrupt 6 | /// 7 | /// Must be aligned with assembly frame format in assembly_esp32 8 | #[repr(C)] 9 | #[allow(non_snake_case)] 10 | #[derive(Debug, Clone, Copy)] 11 | pub struct Context { 12 | pub PC: u32, 13 | pub PS: u32, 14 | 15 | pub A0: u32, 16 | pub A1: u32, 17 | pub A2: u32, 18 | pub A3: u32, 19 | pub A4: u32, 20 | pub A5: u32, 21 | pub A6: u32, 22 | pub A7: u32, 23 | pub A8: u32, 24 | pub A9: u32, 25 | pub A10: u32, 26 | pub A11: u32, 27 | pub A12: u32, 28 | pub A13: u32, 29 | pub A14: u32, 30 | pub A15: u32, 31 | pub SAR: u32, 32 | pub EXCCAUSE: u32, 33 | pub EXCVADDR: u32, 34 | pub LBEG: u32, 35 | pub LEND: u32, 36 | pub LCOUNT: u32, 37 | pub THREADPTR: u32, 38 | pub SCOMPARE1: u32, 39 | pub BR: u32, 40 | pub ACCLO: u32, 41 | pub ACCHI: u32, 42 | pub M0: u32, 43 | pub M1: u32, 44 | pub M2: u32, 45 | pub M3: u32, 46 | pub F64R_LO: u32, 47 | pub F64R_HI: u32, 48 | pub F64S: u32, 49 | pub FCR: u32, 50 | pub FSR: u32, 51 | pub F0: u32, 52 | pub F1: u32, 53 | pub F2: u32, 54 | pub F3: u32, 55 | pub F4: u32, 56 | pub F5: u32, 57 | pub F6: u32, 58 | pub F7: u32, 59 | pub F8: u32, 60 | pub F9: u32, 61 | pub F10: u32, 62 | pub F11: u32, 63 | pub F12: u32, 64 | pub F13: u32, 65 | pub F14: u32, 66 | pub F15: u32, 67 | } 68 | 69 | extern "Rust" { 70 | /// The exception assembly jumps here once registers have been spilled 71 | fn __exception(cause: ExceptionCause, save_frame: &mut Context); 72 | /// This symbol will be provided by the user via `#[exception]` 73 | fn __user_exception(cause: ExceptionCause, save_frame: &mut Context); 74 | /// No attribute is supplied for this symbol as the double exception can hardly occur 75 | fn __double_exception(cause: ExceptionCause, save_frame: &mut Context); 76 | 77 | /// This symbol will be provided by the user via `#[interrupt(1)]` 78 | fn __level_1_interrupt(level: u32, save_frame: &mut Context); 79 | /// This symbol will be provided by the user via `#[interrupt(2)]` 80 | fn __level_2_interrupt(level: u32, save_frame: &mut Context); 81 | /// This symbol will be provided by the user via `#[interrupt(3)]` 82 | fn __level_3_interrupt(level: u32, save_frame: &mut Context); 83 | /// This symbol will be provided by the user via `#[interrupt(4)]` 84 | fn __level_4_interrupt(level: u32, save_frame: &mut Context); 85 | /// This symbol will be provided by the user via `#[interrupt(5)]` 86 | fn __level_5_interrupt(level: u32, save_frame: &mut Context); 87 | /// This symbol will be provided by the user via `#[interrupt(6)]` 88 | fn __level_6_interrupt(level: u32, save_frame: &mut Context); 89 | /// This symbol will be provided by the user via `#[interrupt(7)]` 90 | fn __level_7_interrupt(level: u32, save_frame: &mut Context); 91 | } 92 | 93 | #[no_mangle] 94 | #[link_section = ".rwtext"] 95 | unsafe extern "C" fn __default_exception(cause: ExceptionCause, save_frame: &mut Context) { 96 | __user_exception(cause, save_frame) 97 | } 98 | 99 | #[no_mangle] 100 | #[link_section = ".rwtext"] 101 | extern "C" fn __default_user_exception(cause: ExceptionCause, save_frame: &Context) { 102 | panic!("Exception: {:?}, {:08x?}", cause, save_frame) 103 | } 104 | 105 | #[no_mangle] 106 | #[link_section = ".rwtext"] 107 | extern "C" fn __default_interrupt(level: u32, save_frame: &Context) { 108 | panic!("Interrupt: {:?}, {:08x?}", level, save_frame) 109 | } 110 | 111 | #[no_mangle] 112 | #[link_section = ".rwtext"] 113 | extern "C" fn __default_double_exception(cause: ExceptionCause, save_frame: &Context) { 114 | panic!("Double Exception: {:?}, {:08x?}", cause, save_frame) 115 | } 116 | 117 | // Raw vector handlers 118 | // 119 | // The interrupt handlers all use special return instructions. 120 | // rust still generates a ret.w instruction, which will never be reached. 121 | // generation of the ret.w can be prevented by using core::intrinsics::unreachable, 122 | // but then a break 15,1 will be generated (which takes 3 bytes instead of 2) or a 'loop {}', 123 | // but then a jump to own address will be generated which is also 3 bytes. 124 | // No way found yet to prevent this generation altogether. 125 | 126 | #[naked] 127 | #[no_mangle] 128 | #[link_section = ".KernelExceptionVector.text"] 129 | unsafe extern "C" fn _KernelExceptionVector() { 130 | asm!( 131 | " 132 | wsr a0, EXCSAVE1 // preserve a0 133 | rsr a0, EXCCAUSE // get exception cause 134 | 135 | beqi a0, 5, .AllocAException 136 | 137 | call0 __naked_kernel_exception 138 | ", 139 | options(noreturn) 140 | ); 141 | } 142 | 143 | #[naked] 144 | #[no_mangle] 145 | #[link_section = ".UserExceptionVector.text"] 146 | unsafe extern "C" fn _UserExceptionVector() { 147 | asm!( 148 | " 149 | wsr a0, EXCSAVE1 // preserve a0 150 | rsr a0, EXCCAUSE // get exception cause 151 | 152 | beqi a0, 5, .AllocAException 153 | 154 | call0 __naked_user_exception 155 | 156 | .AllocAException: 157 | call0 _AllocAException 158 | ", 159 | options(noreturn) 160 | ); 161 | } 162 | 163 | #[naked] 164 | #[no_mangle] 165 | #[link_section = ".DoubleExceptionVector.text"] 166 | unsafe extern "C" fn _DoubleExceptionVector() { 167 | asm!( 168 | " 169 | wsr a0, EXCSAVE1 // preserve a0 (EXCSAVE1 can be reused as long as there 170 | // is no double exception in the first exception until 171 | // EXCSAVE1 is stored to the stack.) 172 | call0 __naked_double_exception // used as long jump 173 | ", 174 | options(noreturn) 175 | ); 176 | } 177 | 178 | #[naked] 179 | #[no_mangle] 180 | #[link_section = ".Level2InterruptVector.text"] 181 | unsafe extern "C" fn _Level2InterruptVector() { 182 | asm!( 183 | " 184 | wsr a0, EXCSAVE2 // preserve a0 185 | call0 __naked_level_2_interrupt // used as long jump 186 | ", 187 | options(noreturn) 188 | ); 189 | } 190 | 191 | #[naked] 192 | #[no_mangle] 193 | #[link_section = ".Level3InterruptVector.text"] 194 | unsafe extern "C" fn _Level3InterruptVector() { 195 | asm!( 196 | " 197 | wsr a0, EXCSAVE3 // preserve a0 198 | call0 __naked_level_3_interrupt // used as long jump 199 | ", 200 | options(noreturn) 201 | ); 202 | } 203 | 204 | #[naked] 205 | #[no_mangle] 206 | #[link_section = ".Level4InterruptVector.text"] 207 | unsafe extern "C" fn _Level4InterruptVector() { 208 | asm!( 209 | " 210 | wsr a0, EXCSAVE4 // preserve a0 211 | call0 __naked_level_4_interrupt // used as long jump 212 | ", 213 | options(noreturn) 214 | ); 215 | } 216 | 217 | #[naked] 218 | #[no_mangle] 219 | #[link_section = ".Level5InterruptVector.text"] 220 | unsafe extern "C" fn _Level5InterruptVector() { 221 | asm!( 222 | " 223 | wsr a0, EXCSAVE5 // preserve a0 224 | call0 __naked_level_5_interrupt // used as long jump 225 | ", 226 | options(noreturn) 227 | ); 228 | } 229 | 230 | #[naked] 231 | #[no_mangle] 232 | #[link_section = ".DebugExceptionVector.text"] 233 | unsafe extern "C" fn _Level6InterruptVector() { 234 | asm!( 235 | " 236 | wsr a0, EXCSAVE6 // preserve a0 237 | call0 __naked_level_6_interrupt // used as long jump 238 | ", 239 | options(noreturn) 240 | ); 241 | } 242 | 243 | #[naked] 244 | #[no_mangle] 245 | #[link_section = ".NMIExceptionVector.text"] 246 | unsafe extern "C" fn _Level7InterruptVector() { 247 | asm!( 248 | " 249 | wsr a0, EXCSAVE7 // preserve a0 250 | call0 __naked_level_7_interrupt // used as long jump 251 | ", 252 | options(noreturn) 253 | ); 254 | } 255 | 256 | #[naked] 257 | #[no_mangle] 258 | #[link_section = ".WindowOverflow4.text"] 259 | unsafe extern "C" fn _WindowOverflow4() { 260 | asm!( 261 | " 262 | s32e a0, a5, -16 263 | s32e a1, a5, -12 264 | s32e a2, a5, -8 265 | s32e a3, a5, -4 266 | rfwo 267 | ", 268 | options(noreturn) 269 | ); 270 | } 271 | 272 | #[naked] 273 | #[no_mangle] 274 | #[link_section = ".WindowUnderflow4.text"] 275 | unsafe extern "C" fn _WindowUnderflow4() { 276 | asm!( 277 | " 278 | l32e a0, a5, -16 279 | l32e a1, a5, -12 280 | l32e a2, a5, -8 281 | l32e a3, a5, -4 282 | rfwu 283 | 284 | // inline the _AllocAException saves on the ret.w for WindowUnderflow4 285 | // this makes that it just fits, which is needed for the bbci instructions 286 | 287 | .align 4 288 | _AllocAException: 289 | rsr a0, WINDOWBASE // grab WINDOWBASE before rotw changes it 290 | rotw -1 // WINDOWBASE goes to a4, new a0-a3 are scratch 291 | rsr a2, PS 292 | extui a3, a2, 8, 4 // XCHAL_PS_OWB_SHIFT, XCHAL_PS_OWB_BITS 293 | xor a3, a3, a4 // bits changed from old to current windowbase 294 | rsr a4, EXCSAVE1 // restore original a0 (now in a4) 295 | slli a3, a3, 8 // XCHAL_PS_OWB_SHIFT 296 | xor a2, a2, a3 // flip changed bits in old window base 297 | wsr a2, PS // update PS.OWB to new window base 298 | rsync 299 | 300 | bbci a4, 31, _WindowUnderflow4 301 | rotw -1 // original a0 goes to a8 302 | bbci a8, 30, _WindowUnderflow8 303 | rotw -1 304 | j _WindowUnderflow12 305 | ", 306 | options(noreturn) 307 | ); 308 | } 309 | 310 | #[naked] 311 | #[no_mangle] 312 | #[link_section = ".WindowOverflow8.text"] 313 | unsafe extern "C" fn _WindowOverflow8() { 314 | asm!( 315 | " 316 | s32e a0, a9, -16 317 | l32e a0, a1, -12 318 | 319 | s32e a1, a9, -12 320 | s32e a2, a9, -8 321 | s32e a3, a9, -4 322 | s32e a4, a0, -32 323 | s32e a5, a0, -28 324 | s32e a6, a0, -24 325 | s32e a7, a0, -20 326 | rfwo 327 | ", 328 | options(noreturn) 329 | ); 330 | } 331 | 332 | #[naked] 333 | #[no_mangle] 334 | #[link_section = ".WindowUnderflow8.text"] 335 | unsafe extern "C" fn _WindowUnderflow8() { 336 | asm!( 337 | " 338 | l32e a0, a9, -16 339 | l32e a1, a9, -12 340 | l32e a2, a9, -8 341 | l32e a7, a1, -12 342 | 343 | l32e a3, a9, -4 344 | l32e a4, a7, -32 345 | l32e a5, a7, -28 346 | l32e a6, a7, -24 347 | l32e a7, a7, -20 348 | rfwu 349 | ", 350 | options(noreturn) 351 | ); 352 | } 353 | 354 | #[naked] 355 | #[no_mangle] 356 | #[link_section = ".WindowOverflow12.text"] 357 | unsafe extern "C" fn _WindowOverflow12() { 358 | asm!( 359 | " 360 | s32e a0, a13, -16 361 | l32e a0, a1, -12 362 | 363 | s32e a1, a13, -12 364 | s32e a2, a13, -8 365 | s32e a3, a13, -4 366 | s32e a4, a0, -48 367 | s32e a5, a0, -44 368 | s32e a6, a0, -40 369 | s32e a7, a0, -36 370 | s32e a8, a0, -32 371 | s32e a9, a0, -28 372 | s32e a10, a0, -24 373 | s32e a11, a0, -20 374 | rfwo 375 | ", 376 | options(noreturn) 377 | ); 378 | } 379 | 380 | #[naked] 381 | #[no_mangle] 382 | #[link_section = ".WindowUnderflow12.text"] 383 | unsafe extern "C" fn _WindowUnderflow12() { 384 | asm!( 385 | " 386 | l32e a0, a13, -16 387 | l32e a1, a13, -12 388 | l32e a2, a13, -8 389 | l32e a11, a1, -12 390 | 391 | l32e a3, a13, -4 392 | l32e a4, a11, -48 393 | l32e a5, a11, -44 394 | l32e a6, a11, -40 395 | l32e a7, a11, -36 396 | l32e a8, a11, -32 397 | l32e a9, a11, -28 398 | l32e a10, a11, -24 399 | l32e a11, a11, -20 400 | rfwu 401 | ", 402 | options(noreturn) 403 | ); 404 | } 405 | -------------------------------------------------------------------------------- /src/exception/esp8266.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | use super::ExceptionCause; 4 | 5 | /// State of the CPU saved when entering exception or interrupt 6 | /// 7 | /// Must be aligned with assembly frame format in assembly_lx106 8 | #[repr(C)] 9 | #[allow(non_snake_case)] 10 | #[derive(Debug, Default)] 11 | pub struct Context { 12 | PC: u32, 13 | PS: u32, 14 | 15 | A0: u32, 16 | A1: u32, 17 | A2: u32, 18 | A3: u32, 19 | A4: u32, 20 | A5: u32, 21 | A6: u32, 22 | A7: u32, 23 | A8: u32, 24 | A9: u32, 25 | A10: u32, 26 | A11: u32, 27 | A12: u32, 28 | A13: u32, 29 | A14: u32, 30 | A15: u32, 31 | SAR: u32, 32 | EXCCAUSE: u32, 33 | EXCVADDR: u32, 34 | } 35 | 36 | extern "Rust" { 37 | /// This symbol will be provided by the user via `#[exception]` 38 | fn __exception(cause: ExceptionCause); 39 | /// No attribute is supplied for this symbol as the double exception can hardly occur 40 | fn __double_exception(cause: ExceptionCause); 41 | 42 | /// This symbol will be provided by the user via `#[interrupt]` 43 | fn __level_1_interrupt(level: u32); 44 | } 45 | 46 | #[no_mangle] 47 | #[link_section = ".rwtext"] 48 | extern "C" fn __default_exception(cause: ExceptionCause, save_frame: &Context) { 49 | panic!("Exception: {:?}, {:08x?}", cause, save_frame) 50 | } 51 | 52 | #[no_mangle] 53 | #[link_section = ".rwtext"] 54 | extern "C" fn __default_double_exception(cause: ExceptionCause, save_frame: &Context) { 55 | panic!("Double Exception: {:?}, {:08x?}", cause, save_frame) 56 | } 57 | #[no_mangle] 58 | #[link_section = ".rwtext"] 59 | extern "C" fn __default_interrupt(_level: u32, _save_frame: &Context) {} 60 | 61 | #[naked] 62 | #[no_mangle] 63 | #[link_section = ".DebugException.text"] 64 | unsafe extern "C" fn _DebugExceptionVector() { 65 | asm!( 66 | " 67 | wsr a0, EXCSAVE1 // preserve a0 68 | call0 __naked_debug_exception // used as long jump 69 | ", 70 | options(noreturn) 71 | ); 72 | } 73 | 74 | #[naked] 75 | #[no_mangle] 76 | #[link_section = ".NMIException.text"] 77 | unsafe extern "C" fn _NMIExceptionVector() { 78 | asm!( 79 | " 80 | wsr a0, EXCSAVE1 // preserve a0 81 | call0 __naked_nmi_exception // used as long jump 82 | ", 83 | options(noreturn) 84 | ); 85 | } 86 | 87 | #[naked] 88 | #[no_mangle] 89 | #[link_section = ".KernelException.text"] 90 | unsafe extern "C" fn _KernelExceptionVector() { 91 | asm!( 92 | " 93 | wsr a0, EXCSAVE1 // preserve a0 94 | 95 | call0 __naked_kernel_exception 96 | ", 97 | options(noreturn) 98 | ); 99 | } 100 | 101 | #[naked] 102 | #[no_mangle] 103 | #[link_section = ".UserException.text"] 104 | unsafe extern "C" fn _UserExceptionVector() { 105 | asm!( 106 | " 107 | wsr a0, EXCSAVE1 // preserve a0 108 | 109 | call0 __naked_user_exception 110 | ", 111 | options(noreturn) 112 | ); 113 | } 114 | 115 | #[naked] 116 | #[no_mangle] 117 | #[link_section = ".DoubleException.text"] 118 | unsafe extern "C" fn _DoubleExceptionVector() { 119 | asm!( 120 | " 121 | wsr a0, EXCSAVE1 // preserve a0 (EXCSAVE1 can be reused as long as there 122 | // is no double exception in the first exception until 123 | // EXCSAVE1 is stored to the stack.) 124 | call0 __naked_double_exception // used as long jump 125 | ", 126 | options(noreturn) 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /src/interrupt.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] 2 | include!(concat!(env!("OUT_DIR"), "/interrupt_level_masks.rs")); 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(asm_experimental_arch, naked_functions)] 3 | #![allow(asm_sub_register)] 4 | // required due to: https://github.com/rust-lang/rust/pull/87324 5 | #![allow(named_asm_labels)] 6 | 7 | use core::arch::asm; 8 | 9 | pub use r0::{init_data, zero_bss}; 10 | pub use xtensa_lx_rt_proc_macros::{entry, exception, interrupt, pre_init}; 11 | 12 | pub mod exception; 13 | pub mod interrupt; 14 | 15 | #[doc(hidden)] 16 | #[no_mangle] 17 | pub unsafe extern "C" fn DefaultPreInit() {} 18 | 19 | #[doc(hidden)] 20 | #[no_mangle] 21 | pub unsafe extern "C" fn Reset() -> ! { 22 | // These symbols come from `link.x` 23 | extern "C" { 24 | static mut _bss_start: u32; 25 | static mut _bss_end: u32; 26 | 27 | static mut _data_start: u32; 28 | static mut _data_end: u32; 29 | static _sidata: u32; 30 | 31 | static mut _init_start: u32; 32 | 33 | } 34 | 35 | extern "Rust" { 36 | // This symbol will be provided by the user via `#[entry]` 37 | fn main() -> !; 38 | 39 | // This symbol will be provided by the user via `#[pre_init]` 40 | fn __pre_init(); 41 | 42 | fn __post_init(); 43 | 44 | fn __zero_bss() -> bool; 45 | 46 | fn __init_data() -> bool; 47 | } 48 | 49 | __pre_init(); 50 | 51 | if __zero_bss() { 52 | r0::zero_bss(&mut _bss_start, &mut _bss_end); 53 | } 54 | 55 | if __init_data() { 56 | r0::init_data(&mut _data_start, &mut _data_end, &_sidata); 57 | } 58 | 59 | // Copy of data segment is done by bootloader 60 | 61 | // According to 4.4.6.2 of the xtensa isa, ccount and compare are undefined on reset, 62 | // set all values to zero to disable 63 | reset_internal_timers(); 64 | 65 | // move vec table 66 | set_vecbase(&_init_start as *const u32); 67 | 68 | __post_init(); 69 | 70 | main(); 71 | } 72 | 73 | #[doc(hidden)] 74 | #[no_mangle] 75 | #[rustfmt::skip] 76 | pub unsafe extern "Rust" fn default_post_init() {} 77 | 78 | // We redefine these functions to avoid pulling in `xtensa-lx` as a dependency: 79 | 80 | #[doc(hidden)] 81 | #[inline] 82 | unsafe fn reset_internal_timers() { 83 | #[cfg(any( 84 | XCHAL_HAVE_TIMER0, 85 | XCHAL_HAVE_TIMER1, 86 | XCHAL_HAVE_TIMER2, 87 | XCHAL_HAVE_TIMER3 88 | ))] 89 | { 90 | let value = 0; 91 | cfg_asm!( 92 | { 93 | #[cfg(XCHAL_HAVE_TIMER0)] 94 | "wsr.ccompare0 {0}", 95 | #[cfg(XCHAL_HAVE_TIMER1)] 96 | "wsr.ccompare1 {0}", 97 | #[cfg(XCHAL_HAVE_TIMER2)] 98 | "wsr.ccompare2 {0}", 99 | #[cfg(XCHAL_HAVE_TIMER3)] 100 | "wsr.ccompare3 {0}", 101 | "isync", 102 | }, in(reg) value, options(nostack)); 103 | } 104 | } 105 | 106 | // CPU Interrupts 107 | extern "C" { 108 | #[cfg(XCHAL_HAVE_TIMER0)] 109 | pub fn Timer0(level: u32, save_frame: &mut crate::exception::Context); 110 | #[cfg(XCHAL_HAVE_TIMER1)] 111 | pub fn Timer1(level: u32, save_frame: &mut crate::exception::Context); 112 | #[cfg(XCHAL_HAVE_TIMER2)] 113 | pub fn Timer2(level: u32, save_frame: &mut crate::exception::Context); 114 | #[cfg(XCHAL_HAVE_TIMER3)] 115 | pub fn Timer3(level: u32, save_frame: &mut crate::exception::Context); 116 | 117 | #[cfg(XCHAL_HAVE_PROFILING)] 118 | pub fn Profiling(level: u32, save_frame: &mut crate::exception::Context); 119 | 120 | #[cfg(XCHAL_HAVE_SOFTWARE0)] 121 | pub fn Software0(level: u32, save_frame: &mut crate::exception::Context); 122 | #[cfg(XCHAL_HAVE_SOFTWARE1)] 123 | pub fn Software1(level: u32, save_frame: &mut crate::exception::Context); 124 | 125 | #[cfg(XCHAL_HAVE_NMI)] 126 | pub fn NMI(level: u32, save_frame: &mut crate::exception::Context); 127 | } 128 | 129 | #[doc(hidden)] 130 | #[inline] 131 | unsafe fn set_vecbase(base: *const u32) { 132 | asm!("wsr.vecbase {0}", in(reg) base, options(nostack)); 133 | } 134 | 135 | #[doc(hidden)] 136 | #[no_mangle] 137 | #[rustfmt::skip] 138 | pub extern "Rust" fn default_mem_hook() -> bool { 139 | true // default to zeroing bss & initializing data 140 | } 141 | 142 | #[macro_export] 143 | macro_rules! cfg_asm { 144 | (@inner, [$($x:tt)*], [$($opts:tt)*], ) => { 145 | asm!($($x)* $($opts)*) 146 | }; 147 | (@inner, [$($x:tt)*], [$($opts:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => { 148 | #[cfg($meta)] 149 | cfg_asm!(@inner, [$($x)* $asm,], [$($opts)*], $($rest)*); 150 | #[cfg(not($meta))] 151 | cfg_asm!(@inner, [$($x)*], [$($opts)*], $($rest)*) 152 | }; 153 | (@inner, [$($x:tt)*], [$($opts:tt)*], $asm:literal, $($rest:tt)*) => { 154 | cfg_asm!(@inner, [$($x)* $asm,], [$($opts)*], $($rest)*) 155 | }; 156 | ({$($asms:tt)*}, $($opts:tt)*) => { 157 | cfg_asm!(@inner, [], [$($opts)*], $($asms)*) 158 | }; 159 | } 160 | -------------------------------------------------------------------------------- /xtensa.in.x: -------------------------------------------------------------------------------- 1 | 2 | /* before memory.x to allow override */ 3 | ENTRY(Reset) 4 | 5 | INCLUDE memory.x 6 | 7 | /* after memory.x to allow override */ 8 | PROVIDE(__pre_init = DefaultPreInit); 9 | PROVIDE(__zero_bss = default_mem_hook); 10 | PROVIDE(__init_data = default_mem_hook); 11 | PROVIDE(__post_init = default_post_init); 12 | 13 | INCLUDE exception.x 14 | 15 | SECTIONS { 16 | .text : ALIGN(4) 17 | { 18 | _stext = .; 19 | . = ALIGN (4); 20 | _text_start = ABSOLUTE(.); 21 | . = ALIGN (4); 22 | *(.literal .text .literal.* .text.*) 23 | _text_end = ABSOLUTE(.); 24 | _etext = .; 25 | } > ROTEXT 26 | 27 | .rodata : ALIGN(4) 28 | { 29 | _rodata_start = ABSOLUTE(.); 30 | . = ALIGN (4); 31 | *(.rodata .rodata.*) 32 | _rodata_end = ABSOLUTE(.); 33 | } > RODATA 34 | 35 | .data : ALIGN(4) 36 | { 37 | _data_start = ABSOLUTE(.); 38 | . = ALIGN (4); 39 | *(.data .data.*) 40 | _data_end = ABSOLUTE(.); 41 | } > RWDATA AT > RODATA 42 | 43 | /* LMA of .data */ 44 | _sidata = LOADADDR(.data); 45 | 46 | .bss (NOLOAD) : ALIGN(4) 47 | { 48 | _bss_start = ABSOLUTE(.); 49 | . = ALIGN (4); 50 | *(.bss .bss.* COMMON) 51 | _bss_end = ABSOLUTE(.); 52 | } > RWDATA 53 | 54 | .noinit (NOLOAD) : ALIGN(4) 55 | { 56 | . = ALIGN(4); 57 | *(.noinit .noinit.*) 58 | } > RWDATA 59 | 60 | .rwtext : ALIGN(4) 61 | { 62 | . = ALIGN (4); 63 | *(.rwtext.literal .rwtext .rwtext.literal.* .rwtext.*) 64 | } > RWTEXT 65 | 66 | /* must be last segment using RWTEXT */ 67 | .text_heap_start (NOLOAD) : ALIGN(4) 68 | { 69 | . = ALIGN (4); 70 | _text_heap_start = ABSOLUTE(.); 71 | } > RWTEXT 72 | 73 | /* must be last segment using RWDATA */ 74 | .heap_start (NOLOAD) : ALIGN(4) 75 | { 76 | . = ALIGN (4); 77 | _heap_start = ABSOLUTE(.); 78 | } > RWDATA 79 | } 80 | --------------------------------------------------------------------------------