├── .gitignore ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── cortex-m-signal ├── Cargo.toml ├── build.rs ├── macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── signal.x └── src │ └── lib.rs ├── futures-app ├── .cargo │ └── config ├── Cargo.toml ├── build.rs ├── memory.x └── src │ └── main.rs ├── futures-executor ├── Cargo.toml └── src │ └── lib.rs ├── generator-app ├── .cargo │ └── config ├── Cargo.toml ├── build.rs ├── memory.x └── src │ └── main.rs └── generator-executor ├── Cargo.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | .gdb_history 4 | Cargo.lock 5 | target/ 6 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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 (c) 2018 Jorge Aparicio 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 | # `no-std-async-experiments` 2 | 3 | > Experiments in `no_std` cooperative multitasking 4 | 5 | This repository contains two executors: `futures-executor` and 6 | `generator-executor`. The former uses the `core::{future,task}` machinery; the 7 | latter builds on top of the `Generator` trait. 8 | 9 | Both executors support running up to N -- for simplicity, we currently hardcode 10 | N = 8 -- concurrent "infinite" tasks. Both executors will sleep (see WFI and WFE 11 | instructions) the microcontroller when no task can make progress. Interrupts, 12 | and other tasks, can wake up other tasks using a lightweight signaling mechanism 13 | (see the `cortex-m-signal` crate). 14 | 15 | Each executor has a simple example (`futures-app` and `generator-app`) which you 16 | can run on your laptop / PC using QEMU. You can run the examples using these 17 | commands: 18 | 19 | ``` console 20 | $ # install `qemu-system-arm` 21 | $ sudo apt-get install qemu-system-arm 22 | 23 | $ cd futures-app 24 | $ cargo run --release 25 | T2 26 | T1 27 | T1 28 | T2 29 | T1 30 | T1 31 | T2 32 | T1 33 | T1 34 | ``` 35 | 36 | ## Observations 37 | 38 | The `generator-executor` has less levels of indirection and produces smaller 39 | binaries. I haven't measure execution time on a device but I expect it'll run 40 | faster than `futures-executor`. 41 | 42 | I expect that async / await functionality will be possible to implement on top 43 | of `generator-executor` without any sort of TLS (Thread Local Storage) or 44 | `static` variable. (`async fn` uses `future::from_generator` which internally 45 | uses a thread-local variable for the waker). 46 | 47 | ## TODO list 48 | 49 | (but don't hold your breath) 50 | 51 | - Prototype out of tree async / await functionality. 52 | 53 | - Figure out how to make an interrupt wake up a task *without* context switching 54 | to the interrupt handler. (Can we use interrupt masking and NVIC.ISPR, 55 | somehow?) 56 | 57 | - Support for finite tasks, but these need `Box`-ing. 58 | 59 | ## License 60 | 61 | Licensed under either of 62 | 63 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 64 | http://www.apache.org/licenses/LICENSE-2.0) 65 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 66 | 67 | at your option. 68 | 69 | ### Contribution 70 | 71 | Unless you explicitly state otherwise, any contribution intentionally submitted 72 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 73 | dual licensed as above, without any additional terms or conditions. 74 | -------------------------------------------------------------------------------- /cortex-m-signal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cortex-m-signal" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "signal" 9 | 10 | [dependencies] 11 | cortex-m-signal-macros = { path = "macros" } 12 | hash32 = "0.1.0" 13 | hash32-derive = "0.1.0" 14 | -------------------------------------------------------------------------------- /cortex-m-signal/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs::File, io::Write, path::PathBuf}; 2 | 3 | fn main() { 4 | // Put the linker script somewhere the linker can find it 5 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 6 | File::create(out.join("signal.x")) 7 | .unwrap() 8 | .write_all(include_bytes!("signal.x")) 9 | .unwrap(); 10 | println!("cargo:rustc-link-search={}", out.display()); 11 | 12 | // Only re-run the build script when signal.x is changed, 13 | // instead of when any part of the source code changes. 14 | println!("cargo:rerun-if-changed=signal.x"); 15 | } 16 | -------------------------------------------------------------------------------- /cortex-m-signal/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cortex-m-signal-macros" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | 6 | [lib] 7 | name = "signal_macros" 8 | proc-macro = true 9 | 10 | [dependencies] 11 | syn = "0.15.13" 12 | quote = "0.6.8" 13 | -------------------------------------------------------------------------------- /cortex-m-signal/macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | extern crate quote; 3 | extern crate syn; 4 | 5 | use proc_macro::TokenStream; 6 | 7 | use quote::quote; 8 | use syn::{parse_macro_input, DeriveInput}; 9 | 10 | /// An attribute to implement the `Signal` trait for a ZST 11 | #[proc_macro_derive(Signal)] 12 | pub fn signal(input: TokenStream) -> TokenStream { 13 | let input = parse_macro_input!(input as DeriveInput); 14 | let ident = &input.ident; 15 | 16 | quote!(unsafe impl signal::Signal for #ident { 17 | fn usize() -> usize { 18 | // This `static` is used to create unique identifiers 19 | // 20 | // See the embedonomicon ("Logging with symbols" chapter) for more details about this 21 | // technique 22 | #[link_section = ".signal"] 23 | static ID: u8 = 0; 24 | 25 | &ID as *const u8 as usize 26 | } 27 | }) 28 | .into() 29 | } 30 | -------------------------------------------------------------------------------- /cortex-m-signal/signal.x: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .signal 0 (INFO) : { 4 | *(.signal); 5 | } 6 | } 7 | 8 | ASSERT(SIZEOF(.signal) <= 32, "only 32 different signals are supported at the moment"); 9 | -------------------------------------------------------------------------------- /cortex-m-signal/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Interrupt-safe, thread-safe signaling mechanism 2 | //! 3 | //! **IMPORTANT**: This crate is only sound if the target is an ARM Cortex-M device that supports 4 | //! bit banding. 5 | //! 6 | //! The main use case for this crate is efficiently waking up the main "thread" from an interrupt 7 | //! handler 8 | //! 9 | //! ``` ignore 10 | //! // Use the `derive(Signal)` attribute to create a new signal 11 | //! #[derive(Signal)] 12 | //! struct A; 13 | //! 14 | //! #[derive(Signal)] 15 | //! struct B; 16 | //! 17 | //! #[entry] 18 | //! fn main() -> ! { 19 | //! loop { 20 | //! // Iterate over the signals that were set 21 | //! for id in Signals::read() { 22 | //! if id == A::id() { 23 | //! // woken up by SysTick 24 | //! // .. 25 | //! } else if id == B::id() { 26 | //! // woken up by TIM6 27 | //! // .. 28 | //! } 29 | //! } 30 | //! 31 | //! // sleep until the next signal is received 32 | //! asm::wfi(); 33 | //! } 34 | //! } 35 | //! 36 | //! #[exception] 37 | //! fn SysTick() { 38 | //! // .. 39 | //! // Wake up `main` 40 | //! A::set(); 41 | //! // .. 42 | //! } 43 | //! 44 | //! #[interrupt] 45 | //! fn TIM6() { 46 | //! // .. 47 | //! // Wake up `main` 48 | //! B::set(); 49 | //! // .. 50 | //! } 51 | //! ``` 52 | //! 53 | //! This is much cheaper than setting one SPSC queue between each interrupt handler and `main`. This 54 | //! is also more efficient than setting up one `AtomicBool` per interrupt. See below: 55 | //! 56 | //! ``` ignore 57 | //! static A: AtomicBool = AtomicBool::new(false); 58 | //! static B: AtomicBool = AtomicBool::new(false); 59 | //! 60 | //! #[entry] 61 | //! fn main() -> ! { 62 | //! loop { 63 | //! if A.swap(false, Ordering::Relaxed) { 64 | //! // woken up by SysTick 65 | //! // .. 66 | //! 67 | //! } 68 | //! 69 | //! if B.swap(false, Ordering::Relaxed) { 70 | //! // woken up by TIM6 71 | //! // .. 72 | //! } 73 | //! 74 | //! // sleep until the next signal is received 75 | //! asm::wfi(); 76 | //! } 77 | //! } 78 | //! 79 | //! #[exception] 80 | //! fn SysTick() { 81 | //! // .. 82 | //! // Wake up `main` 83 | //! A.store(true, Ordering::Relaxed); 84 | //! // .. 85 | //! } 86 | //! 87 | //! #[interrupt] 88 | //! fn TIM6() { 89 | //! // .. 90 | //! // Wake up `main` 91 | //! B.store(true, Ordering::Relaxed); 92 | //! // .. 93 | //! } 94 | //! 95 | //! ``` 96 | 97 | #![deny(missing_docs)] 98 | #![deny(warnings)] 99 | #![no_std] 100 | 101 | use core::sync::atomic::{AtomicUsize, Ordering}; 102 | 103 | use hash32_derive::Hash32; 104 | pub use signal_macros::Signal; 105 | 106 | /// A signal identifier 107 | #[derive(Clone, Copy, Debug, Eq, Hash32, PartialEq)] 108 | pub struct Id(u8); 109 | 110 | impl From for u8 { 111 | fn from(id: Id) -> u8 { 112 | id.0 113 | } 114 | } 115 | 116 | /// A snapshot of the signals that *were* set 117 | #[derive(Clone, Copy, Debug)] 118 | pub struct Signals(usize); 119 | 120 | impl Signals { 121 | /// Returns a snapshot of the signals currently set 122 | /// 123 | /// **NOTE**: this will clear all the currently set signals 124 | pub fn read() -> Self { 125 | // NOTE(Ordering::Relaxed) we assume a single core target 126 | Signals(SIGNALS.swap(0, Ordering::Relaxed)) 127 | } 128 | 129 | /// Returns `true` if no signal is set in this snapshot 130 | pub fn is_empty(&self) -> bool { 131 | self.0 == 0 132 | } 133 | } 134 | 135 | impl Iterator for Signals { 136 | type Item = Id; 137 | 138 | fn next(&mut self) -> Option { 139 | if self.0 == 0 { 140 | None 141 | } else { 142 | let pos = 31 - self.0.leading_zeros() as u8; 143 | self.0 &= !(1 << pos); 144 | Some(Id(pos)) 145 | } 146 | } 147 | } 148 | 149 | static SIGNALS: AtomicUsize = AtomicUsize::new(0); 150 | 151 | // Bit banding 152 | const RAM_START: usize = 0x2000_0000; 153 | const ALIAS_START: usize = 0x2200_0000; 154 | 155 | /// A unique signal type 156 | pub unsafe trait Signal { 157 | /// The identifier for this signal 158 | fn id() -> Id { 159 | Id(Self::usize() as u8) 160 | } 161 | 162 | /// IMPLEMENTATION DETAIL. DO NOT USE 163 | #[doc(hidden)] 164 | fn ptr() -> *const AtomicUsize { 165 | let id = Self::usize(); 166 | let p = &SIGNALS as *const AtomicUsize as usize; 167 | ((32 * p.wrapping_sub(RAM_START)).wrapping_add(ALIAS_START) + 4 * id) as *const AtomicUsize 168 | } 169 | 170 | /// Sets this signal 171 | fn set() { 172 | unsafe { 173 | // NOTE(Ordering::Relaxed) we assume a single core target 174 | AtomicUsize::store(&*Self::ptr(), 1, Ordering::Relaxed); 175 | } 176 | } 177 | 178 | /// IMPLEMENTATION DETAIL. DO NOT USE 179 | #[doc(hidden)] 180 | fn usize() -> usize; 181 | } 182 | -------------------------------------------------------------------------------- /futures-app/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | # execute programs on QEMU 3 | runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" 4 | 5 | rustflags = [ 6 | "-C", "link-arg=-Tlink.x", 7 | "-C", "link-arg=-Tsignal.x", 8 | ] 9 | 10 | [build] 11 | target = "thumbv7m-none-eabi" # Cortex-M3 12 | -------------------------------------------------------------------------------- /futures-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | readme = "README.md" 5 | name = "app" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | cortex-m = "0.5.8" 10 | cortex-m-rt = "0.6.5" 11 | cortex-m-semihosting = "0.3.2" 12 | cortex-m-signal = { path = "../cortex-m-signal" } 13 | futures-executor = { path = "../futures-executor" } 14 | pin-utils = "0.1.0-alpha.3" 15 | 16 | [dependencies.panic-semihosting] 17 | features = ["exit"] 18 | version = "0.5.1" 19 | 20 | [profile.release] 21 | codegen-units = 1 # better optimizations 22 | debug = true # symbols are nice and they don't increase the size on Flash 23 | lto = true # better optimizations 24 | -------------------------------------------------------------------------------- /futures-app/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | // Put the linker script somewhere the linker can find it 8 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 9 | File::create(out.join("memory.x")) 10 | .unwrap() 11 | .write_all(include_bytes!("memory.x")) 12 | .unwrap(); 13 | println!("cargo:rustc-link-search={}", out.display()); 14 | 15 | // Only re-run the build script when memory.x is changed, 16 | // instead of when any part of the source code changes. 17 | println!("cargo:rerun-if-changed=memory.x"); 18 | } 19 | -------------------------------------------------------------------------------- /futures-app/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | /* TODO Adjust these memory regions to match your device memory layout */ 5 | /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */ 6 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 7 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 8 | } 9 | 10 | /* This is where the call stack will be allocated. */ 11 | /* The stack is of the full descending type. */ 12 | /* You may want to use this variable to locate the call stack and static 13 | variables in different memory regions. Below is shown the default value */ 14 | /* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ 15 | 16 | /* You can use this symbol to customize the location of the .text section */ 17 | /* If omitted the .text section will be placed right after the .vector_table 18 | section */ 19 | /* This is required only on microcontrollers that store some configuration right 20 | after the vector table */ 21 | /* _stext = ORIGIN(FLASH) + 0x400; */ 22 | 23 | /* Example of putting non-initialized variables into custom RAM locations. */ 24 | /* This assumes you have defined a region RAM2 above, and in the Rust 25 | sources added the attribute `#[link_section = ".ram2bss"]` to the data 26 | you want to place there. */ 27 | /* Note that the section will not be zero-initialized by the runtime! */ 28 | /* SECTIONS { 29 | .ram2bss (NOLOAD) : ALIGN(4) { 30 | *(.ram2bss); 31 | . = ALIGN(4); 32 | } > RAM2 33 | } INSERT AFTER .bss; 34 | */ 35 | -------------------------------------------------------------------------------- /futures-app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![feature(arbitrary_self_types)] 3 | #![feature(futures_api)] 4 | #![feature(never_type)] 5 | #![feature(pin)] 6 | #![no_main] 7 | #![no_std] 8 | 9 | // panicking behavior 10 | extern crate panic_semihosting; 11 | 12 | use core::{ 13 | future::Future, 14 | pin::Pin, 15 | task::{LocalWaker, Poll}, 16 | }; 17 | 18 | use cortex_m::peripheral::syst::SystClkSource; 19 | use cortex_m_rt::{entry, exception}; 20 | use cortex_m_semihosting::hprintln; 21 | use executor::{Executor, Router}; 22 | use pin_utils::pin_mut; 23 | use signal::Signal; 24 | 25 | #[entry] 26 | fn main() -> ! { 27 | let p = cortex_m::Peripherals::take().unwrap(); 28 | 29 | // configures the system timer to trigger a SysTick exception every second 30 | let mut syst = p.SYST; 31 | syst.set_clock_source(SystClkSource::Core); 32 | syst.set_reload(12_000_000); // period = 1s 33 | syst.enable_counter(); 34 | syst.enable_interrupt(); 35 | 36 | let router = &Router::new(); 37 | let executor = Executor::new(router); 38 | pin_mut!(executor); 39 | 40 | let t1 = T1::new(router); 41 | pin_mut!(t1); 42 | executor.as_mut().spawn(t1).ok(); 43 | 44 | let t2 = T2::new(router); 45 | pin_mut!(t2); 46 | executor.as_mut().spawn(t2).ok(); 47 | 48 | executor.run() 49 | } 50 | 51 | #[exception] 52 | fn SysTick() { 53 | static mut COUNT: u8 = 0; 54 | 55 | *COUNT += 1; 56 | 57 | // Send signal A every second 58 | A::set(); 59 | if *COUNT % 2 == 0 { 60 | // Send signal B every 2 seconds 61 | B::set(); 62 | } 63 | } 64 | 65 | struct T1<'a> { 66 | router: &'a Router, 67 | } 68 | 69 | impl<'a> T1<'a> { 70 | fn new(router: &'a Router) -> Self { 71 | T1 { router } 72 | } 73 | } 74 | 75 | impl<'a> Future for T1<'a> { 76 | type Output = !; 77 | 78 | fn poll(self: Pin<&mut Self>, lw: &LocalWaker) -> Poll { 79 | hprintln!("T1").unwrap(); 80 | 81 | // Sleep this task until signal A is received 82 | self.router.route(A::id(), lw.clone()); 83 | Poll::Pending 84 | } 85 | } 86 | 87 | struct T2<'a> { 88 | router: &'a Router, 89 | } 90 | 91 | impl<'a> T2<'a> { 92 | fn new(router: &'a Router) -> Self { 93 | T2 { router } 94 | } 95 | } 96 | 97 | impl<'a> Future for T2<'a> { 98 | type Output = !; 99 | 100 | fn poll(self: Pin<&mut Self>, lw: &LocalWaker) -> Poll { 101 | hprintln!("T2").unwrap(); 102 | 103 | // Sleep this task until signal B is received 104 | self.router.route(B::id(), lw.clone()); 105 | Poll::Pending 106 | } 107 | } 108 | 109 | #[derive(Signal)] 110 | struct A; 111 | 112 | #[derive(Signal)] 113 | struct B; 114 | -------------------------------------------------------------------------------- /futures-executor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "futures-executor" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "executor" 9 | 10 | [dependencies] 11 | heapless = "0.4.0" 12 | cortex-m-signal = { path = "../cortex-m-signal" } 13 | 14 | [dependencies.cortex-m] 15 | features = ["inline-asm"] 16 | version = "0.5.8" 17 | -------------------------------------------------------------------------------- /futures-executor/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(arbitrary_self_types)] 2 | #![feature(futures_api)] 3 | #![feature(never_type)] 4 | #![feature(pin)] 5 | #![no_std] 6 | 7 | use core::{ 8 | cell::{RefCell, UnsafeCell}, 9 | future::Future, 10 | hint, 11 | pin::Pin, 12 | ptr::NonNull, 13 | task::{LocalWaker, UnsafeWake, Waker}, 14 | }; 15 | 16 | use cortex_m::asm; 17 | use heapless::{consts, spsc::Queue, FnvIndexMap, Vec}; 18 | use signal::{Id, Signals}; 19 | 20 | // For simplicity we hardcode the capacity of the executor 21 | // 22 | // The capacity could be generic but the trait bounds are annoying to write 23 | type SIZE = consts::U8; 24 | 25 | /// Routes signals to tasks 26 | pub struct Router { 27 | wakers: RefCell>, 28 | } 29 | 30 | impl Router { 31 | #[inline] 32 | pub fn new() -> Self { 33 | Router { 34 | wakers: RefCell::new(FnvIndexMap::new()), 35 | } 36 | } 37 | 38 | /// Routes the given `signal` to the given `waker` 39 | /// 40 | /// When the `signal` is set the `waker` will be used to wake up a task 41 | #[inline] 42 | pub fn route(&self, signal: Id, waker: LocalWaker) { 43 | self.wakers.borrow_mut().insert(signal, waker).ok(); 44 | } 45 | 46 | #[inline] 47 | fn wake(&self, signal: Id) { 48 | if let Some(waker) = self.wakers.borrow_mut().remove(&signal) { 49 | waker.wake(); 50 | } 51 | } 52 | } 53 | 54 | struct Task { 55 | id: u8, 56 | // XXX kind of wasteful because all `Task`s will have the same pointer 57 | ready_queue: NonNull>>, 58 | } 59 | 60 | // HACK Task is NOT Send or Sync but the UnsafeWake trait requires these so ... 61 | unsafe impl Send for Task {} 62 | unsafe impl Sync for Task {} 63 | 64 | unsafe impl UnsafeWake for Task { 65 | #[inline] 66 | unsafe fn clone_raw(&self) -> Waker { 67 | Waker::new(NonNull::from(self as &UnsafeWake)) 68 | } 69 | 70 | #[inline] 71 | unsafe fn drop_raw(&self) {} 72 | 73 | #[inline] 74 | unsafe fn wake(&self) { 75 | unreachable!() 76 | } 77 | 78 | #[inline] 79 | unsafe fn wake_local(&self) { 80 | (*self.ready_queue.as_ref().get()).enqueue_unchecked(self.id) 81 | } 82 | } 83 | 84 | pub struct Executor<'a> { 85 | // all futures 86 | futures: Vec>, SIZE>, 87 | // all "tasks" 88 | // XXX we only need this because LocalWakers need to point to something ... 89 | tasks: Vec, 90 | // queue of ready tasks (IDs only) 91 | ready_queue: UnsafeCell>, 92 | router: &'a Router, 93 | } 94 | 95 | impl<'a> Executor<'a> { 96 | #[inline] 97 | pub fn new(router: &'a Router) -> Self { 98 | Executor { 99 | futures: Vec::new(), 100 | tasks: Vec::new(), 101 | ready_queue: UnsafeCell::new(Queue::new()), 102 | router, 103 | } 104 | } 105 | 106 | /// Spawns the given `fut`-ure as a task 107 | /// 108 | /// Note that the task won't start or make progress until `run` is called 109 | #[inline] 110 | pub fn spawn(mut self: Pin<&mut Self>, fut: Pin<&'a mut Future>) -> Result<(), ()> { 111 | let id = self.tasks.len() as u8; 112 | 113 | self.futures.push(fut).map_err(drop)?; 114 | 115 | // NOTE(NonNull) OK because `self` is pinned thus `ready_queue` is also immovable 116 | let nn = NonNull::from(&self.ready_queue); 117 | self.tasks 118 | .push(Task { 119 | id, 120 | ready_queue: nn, 121 | }) 122 | .unwrap_or_else(|_| unsafe { hint::unreachable_unchecked() }); 123 | 124 | unsafe { 125 | (*self.ready_queue.get()).enqueue_unchecked(id); 126 | } 127 | 128 | Ok(()) 129 | } 130 | 131 | /// Runs all the spawned tasks 132 | #[inline] 133 | pub fn run(mut self: Pin<&mut Self>) -> ! { 134 | loop { 135 | unsafe { 136 | // advance ready tasks 137 | while let Some(id) = (*self.ready_queue.get()).dequeue() { 138 | let task = self.tasks.as_ptr().add(usize::from(id)) as *mut Task; 139 | 140 | // NOTE(NonNull) OK because `self` is pinned thus `tasks` is also immovable 141 | let lw = LocalWaker::new(NonNull::new_unchecked(task)); 142 | 143 | self.futures 144 | .get_unchecked_mut(usize::from(id)) 145 | .as_mut() 146 | .poll(&lw); 147 | } 148 | 149 | // wait for a signal 150 | let mut signals; 151 | loop { 152 | signals = Signals::read(); 153 | 154 | if !signals.is_empty() { 155 | break; 156 | } 157 | 158 | asm::wfe(); 159 | } 160 | 161 | for id in signals { 162 | self.router.wake(id); 163 | } 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /generator-app/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | # execute programs on QEMU 3 | runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" 4 | 5 | rustflags = [ 6 | "-C", "link-arg=-Tlink.x", 7 | "-C", "link-arg=-Tsignal.x", 8 | ] 9 | 10 | [build] 11 | target = "thumbv7m-none-eabi" # Cortex-M3 12 | -------------------------------------------------------------------------------- /generator-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | readme = "README.md" 5 | name = "app" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | cortex-m = "0.5.8" 10 | cortex-m-rt = "0.6.5" 11 | cortex-m-semihosting = "0.3.2" 12 | cortex-m-signal = { path = "../cortex-m-signal" } 13 | generator-executor = { path = "../generator-executor" } 14 | 15 | [dependencies.panic-semihosting] 16 | features = ["exit"] 17 | version = "0.5.1" 18 | 19 | [profile.release] 20 | codegen-units = 1 # better optimizations 21 | debug = true # symbols are nice and they don't increase the size on Flash 22 | lto = true # better optimizations 23 | -------------------------------------------------------------------------------- /generator-app/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | // Put the linker script somewhere the linker can find it 8 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 9 | File::create(out.join("memory.x")) 10 | .unwrap() 11 | .write_all(include_bytes!("memory.x")) 12 | .unwrap(); 13 | println!("cargo:rustc-link-search={}", out.display()); 14 | 15 | // Only re-run the build script when memory.x is changed, 16 | // instead of when any part of the source code changes. 17 | println!("cargo:rerun-if-changed=memory.x"); 18 | } 19 | -------------------------------------------------------------------------------- /generator-app/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | /* TODO Adjust these memory regions to match your device memory layout */ 5 | /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */ 6 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 7 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 8 | } 9 | 10 | /* This is where the call stack will be allocated. */ 11 | /* The stack is of the full descending type. */ 12 | /* You may want to use this variable to locate the call stack and static 13 | variables in different memory regions. Below is shown the default value */ 14 | /* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ 15 | 16 | /* You can use this symbol to customize the location of the .text section */ 17 | /* If omitted the .text section will be placed right after the .vector_table 18 | section */ 19 | /* This is required only on microcontrollers that store some configuration right 20 | after the vector table */ 21 | /* _stext = ORIGIN(FLASH) + 0x400; */ 22 | 23 | /* Example of putting non-initialized variables into custom RAM locations. */ 24 | /* This assumes you have defined a region RAM2 above, and in the Rust 25 | sources added the attribute `#[link_section = ".ram2bss"]` to the data 26 | you want to place there. */ 27 | /* Note that the section will not be zero-initialized by the runtime! */ 28 | /* SECTIONS { 29 | .ram2bss (NOLOAD) : ALIGN(4) { 30 | *(.ram2bss); 31 | . = ALIGN(4); 32 | } > RAM2 33 | } INSERT AFTER .bss; 34 | */ 35 | -------------------------------------------------------------------------------- /generator-app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![feature(generators)] 3 | #![feature(never_type)] 4 | #![no_main] 5 | #![no_std] 6 | 7 | // panicking behavior 8 | extern crate panic_semihosting; 9 | 10 | use cortex_m::peripheral::syst::SystClkSource; 11 | use cortex_m_rt::{entry, exception}; 12 | use cortex_m_semihosting::hprintln; 13 | use executor::Executor; 14 | use signal::Signal; 15 | 16 | #[entry] 17 | fn main() -> ! { 18 | let p = cortex_m::Peripherals::take().unwrap(); 19 | 20 | // configures the system timer to trigger a SysTick exception every second 21 | let mut syst = p.SYST; 22 | syst.set_clock_source(SystClkSource::Core); 23 | syst.set_reload(12_000_000); // period = 1s 24 | syst.enable_counter(); 25 | syst.enable_interrupt(); 26 | 27 | let mut t1 = || { 28 | loop { 29 | hprintln!("T1").unwrap(); 30 | 31 | // Sleep this task until signal A is received 32 | yield A::id(); 33 | } 34 | }; 35 | 36 | let mut t2 = || { 37 | loop { 38 | hprintln!("T2").unwrap(); 39 | 40 | // Sleep this task until signal B is received 41 | yield B::id(); 42 | } 43 | }; 44 | 45 | let mut executor = Executor::new(); 46 | executor.spawn(&mut t1).ok(); 47 | executor.spawn(&mut t2).ok(); 48 | 49 | executor.run() 50 | } 51 | 52 | #[exception] 53 | fn SysTick() { 54 | static mut COUNT: u8 = 0; 55 | 56 | *COUNT += 1; 57 | 58 | // Send signal A every second 59 | A::set(); 60 | if *COUNT % 2 == 0 { 61 | // Send signal B every 2 seconds 62 | B::set(); 63 | } 64 | } 65 | 66 | #[derive(Signal)] 67 | struct A; 68 | 69 | #[derive(Signal)] 70 | struct B; 71 | -------------------------------------------------------------------------------- /generator-executor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generator-executor" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "executor" 9 | 10 | [dependencies] 11 | heapless = "0.4.0" 12 | cortex-m-signal = { path = "../cortex-m-signal" } 13 | cortex-m = "0.5.8" 14 | -------------------------------------------------------------------------------- /generator-executor/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(generator_trait)] 2 | #![feature(never_type)] 3 | #![feature(pin)] 4 | #![no_std] 5 | 6 | use core::{ 7 | hint, 8 | ops::{Generator, GeneratorState}, 9 | }; 10 | 11 | use cortex_m::asm; 12 | use heapless::{consts, spsc::Queue, FnvIndexMap, Vec}; 13 | use signal::{Id, Signals}; 14 | 15 | // For simplicity we hard-code the capacity of the Executor 16 | type SIZE = consts::U8; 17 | 18 | pub struct Executor<'a> { 19 | // FIXME(rust-lang/rust#55704) these should be pinned generators 20 | generators: Vec<&'a mut Generator, SIZE>, 21 | /// Routes signals to tasks 22 | // XXX this could be replaced with `[Option; 32]` to get faster execution time (no hashing) 23 | // at the expense of using more stack memory 24 | router: FnvIndexMap, 25 | /// Ready tasks 26 | ready: Queue, 27 | } 28 | 29 | impl<'a> Executor<'a> { 30 | pub fn new() -> Self { 31 | Executor { 32 | generators: Vec::new(), 33 | router: FnvIndexMap::new(), 34 | ready: Queue::new(), 35 | } 36 | } 37 | 38 | pub fn spawn(&mut self, gen: &'a mut Generator) -> Result<(), ()> { 39 | let id = self.generators.len(); 40 | self.generators.push(gen).map_err(drop)?; 41 | unsafe { 42 | // NOTE(enqueue_unchecked) we can never exceed the capacity of `ready` 43 | self.ready 44 | .enqueue_unchecked(id as u8) 45 | } 46 | Ok(()) 47 | } 48 | 49 | pub fn run(&mut self) -> ! { 50 | loop { 51 | // dispatch ready tasks 52 | while let Some(i) = self.ready.dequeue() { 53 | unsafe { 54 | if let GeneratorState::Yielded(id) = 55 | self.generators.get_unchecked_mut(usize::from(i)).resume() 56 | { 57 | // NOTE(unchecked_unreachable) we can never exceed the capacity of `router` 58 | self.router 59 | .insert(id, i as u8) 60 | .unwrap_or_else(|_| hint::unreachable_unchecked()); 61 | } 62 | } 63 | } 64 | 65 | let mut signals; 66 | loop { 67 | signals = Signals::read(); 68 | 69 | if !signals.is_empty() { 70 | break; 71 | } 72 | 73 | asm::wfe(); 74 | } 75 | 76 | for id in signals { 77 | if let Some(i) = self.router.remove(&id) { 78 | unsafe { 79 | // NOTE(enqueue_unchecked) we can never exceed the capacity of `ready` 80 | self.ready.enqueue_unchecked(i); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | --------------------------------------------------------------------------------