├── .cargo └── config ├── .github └── workflows │ └── ci.yml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── firmware ├── .cargo │ └── config ├── .gitignore ├── Cargo.toml ├── README.md ├── apps │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── bin │ │ ├── abort.rs │ │ ├── acm.rs │ │ ├── blinky.rs │ │ ├── core-panic.rs │ │ ├── deviceid.rs │ │ ├── fmt.rs │ │ ├── hard-fault.rs │ │ ├── heartbeat.rs │ │ ├── hello.rs │ │ ├── hid.rs │ │ ├── led.rs │ │ ├── log.rs │ │ ├── loopback.rs │ │ ├── radio.rs │ │ └── stack-overflow.rs ├── asm │ ├── Cargo.toml │ ├── asm.s │ ├── assemble.sh │ ├── bin │ │ └── thumbv7em-none-eabi.a │ ├── build.rs │ └── src │ │ └── lib.rs ├── async-core │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── task.rs │ │ ├── unsync.rs │ │ └── unsync │ │ ├── mutex.rs │ │ └── spsc.rs ├── drivers │ └── mrf24j40 │ │ ├── Cargo.toml │ │ └── src │ │ ├── lib.rs │ │ ├── long.rs │ │ ├── reg.rs │ │ └── short.rs ├── executor │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── hal │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── interrupts.x │ ├── link-flash.x │ ├── link-ram.x │ └── src │ │ ├── atomic.rs │ │ ├── clock.rs │ │ ├── errata.rs │ │ ├── led.rs │ │ ├── lib.rs │ │ ├── mem.rs │ │ ├── p0.rs │ │ ├── radio.rs │ │ ├── reset.rs │ │ ├── spi.rs │ │ ├── time.rs │ │ ├── timer.rs │ │ ├── usbd.rs │ │ └── util.rs ├── pac │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── panic-abort │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── pool │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── ring │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── semidap │ ├── Cargo.toml │ ├── README.md │ ├── asm.s │ ├── assemble.sh │ ├── bin │ │ └── thumbv7em-none-eabi.a │ ├── build.rs │ └── src │ │ └── lib.rs ├── tasks │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── tests │ ├── Cargo.toml │ └── src │ └── bin │ ├── assert.rs │ ├── async-channel.rs │ ├── async-mutex.rs │ ├── async-yield.rs │ ├── binfmt.rs │ ├── exception.rs │ ├── nested.rs │ ├── panic.rs │ └── wfe.rs ├── host ├── .gitignore ├── Cargo.toml ├── README.md ├── acm-cat │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── binfmt-parser │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cmsis-dap │ ├── Cargo.toml │ └── src │ │ ├── adiv5.rs │ │ ├── ahb_ap.rs │ │ ├── cortex_m.rs │ │ ├── dap.rs │ │ ├── hid.rs │ │ ├── lib.rs │ │ ├── sealed.rs │ │ └── util.rs ├── executor-macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── hidc │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── regen │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── cm.rs │ │ ├── codegen.rs │ │ ├── codegen │ │ └── util.rs │ │ ├── fmt.rs │ │ ├── ir.rs │ │ ├── main.rs │ │ ├── opt.rs │ │ ├── translate.rs │ │ ├── translate │ │ └── svd.rs │ │ └── verify.rs ├── semidap │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── semiprobe │ ├── Cargo.toml │ └── src │ │ └── main.rs └── tasks-macros │ ├── Cargo.toml │ └── src │ └── lib.rs └── shared ├── .gitignore ├── Cargo.toml ├── binfmt ├── Cargo.toml ├── README.md ├── macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── src │ ├── derive.rs │ ├── impls.rs │ ├── lib.rs │ └── util.rs ├── cm ├── Cargo.toml └── src │ └── lib.rs ├── consts ├── Cargo.toml └── src │ └── lib.rs └── usb2 ├── Cargo.toml └── src ├── cdc.rs ├── cdc ├── acm.rs ├── call.rs ├── header.rs └── union.rs ├── config.rs ├── device.rs ├── ep.rs ├── iface.rs └── lib.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | bb = "build --bin" 3 | br = "build --release" 4 | brb = "build --release --bin" 5 | rb = "run --bin" 6 | re = "run --example" 7 | rr = "run --release" 8 | rrb = "run --release --bin" 9 | rre = "run --release --example" 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | name: Continuous Integration 4 | 5 | jobs: 6 | check: 7 | name: check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | override: true 17 | profile: minimal 18 | target: thumbv7em-none-eabi 19 | toolchain: beta # TODO stable when 1.44 is out 20 | 21 | - name: Fetch SVD 22 | working-directory: ./host/regen 23 | run: | 24 | curl -LO https://raw.githubusercontent.com/NordicSemiconductor/nrfx/master/mdk/nrf52840.svd 25 | sed -i 's|read-writeonce|read-writeOnce|g' nrf52840.svd 26 | 27 | - name: Generate PAC 28 | uses: marcopolo/cargo@master 29 | with: 30 | command: run 31 | working-directory: ./host/regen 32 | 33 | - name: Install build dependencies (host) 34 | run: | 35 | sudo apt-get update 36 | sudo apt-get install libusb-1.0-0-dev 37 | 38 | - name: Check host 39 | uses: marcopolo/cargo@master 40 | with: 41 | command: check 42 | working-directory: ./host 43 | 44 | - name: Check firmware 45 | uses: marcopolo/cargo@master 46 | with: 47 | command: check 48 | working-directory: ./firmware 49 | 50 | lints: 51 | name: lint 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v2 55 | - uses: actions-rs/toolchain@v1 56 | with: 57 | components: rustfmt, clippy 58 | override: true 59 | profile: minimal 60 | target: thumbv7em-none-eabi 61 | toolchain: nightly 62 | 63 | - name: Format firmware 64 | uses: marcopolo/cargo@master 65 | with: 66 | command: fmt 67 | args: --all -- --check 68 | working-directory: ./firmware 69 | 70 | - name: Format host 71 | uses: marcopolo/cargo@master 72 | with: 73 | command: fmt 74 | args: --all -- --check 75 | working-directory: ./host 76 | 77 | - name: Format shared 78 | uses: marcopolo/cargo@master 79 | with: 80 | command: fmt 81 | args: --all -- --check 82 | working-directory: ./shared 83 | 84 | - name: Fetch SVD 85 | working-directory: ./host/regen 86 | run: | 87 | curl -LO https://raw.githubusercontent.com/NordicSemiconductor/nrfx/master/mdk/nrf52840.svd 88 | sed -i 's|read-writeonce|read-writeOnce|g' nrf52840.svd 89 | 90 | - name: Generate PAC 91 | uses: marcopolo/cargo@master 92 | with: 93 | command: run 94 | working-directory: ./host/regen 95 | 96 | - name: Install build dependencies (host) 97 | run: | 98 | sudo apt-get update 99 | sudo apt-get install libusb-1.0-0-dev 100 | 101 | - name: Clippy host 102 | uses: marcopolo/cargo@master 103 | with: 104 | command: clippy 105 | args: -- -D warnings 106 | working-directory: ./host 107 | 108 | - name: Clippy firmware 109 | uses: marcopolo/cargo@master 110 | with: 111 | command: clippy 112 | args: -- -D warnings 113 | working-directory: ./firmware 114 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 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 | Hey, how time flies! It's now 2021 (at time of writing). This experiment is over and the repository has been archived. Some of the ideas here live on as part of the [knurling project](https://github.com/knurling-rs). 2 | 3 | --- 4 | 5 | # `embedded2020` 6 | 7 | > A fresh look at embedded Rust development 8 | 9 | ## Please read this 10 | 11 | The goal of this personal experiment is exploring ways in which developing 12 | embedded Rust software (HALs, device-agnostic libraries, applications, etc.) can 13 | be improved. You'll see me here re-implementing things that have already been 14 | published on crates.io (specially things that require reading a manual or a 15 | standard). That's pretty much intentional. The goal is improving how those 16 | things are developed so pulling them as dependencies from crates.io would defeat 17 | the purpose. IOW, the goal here is studying the *process* of making software and 18 | not just making some application or library. 19 | 20 | All the code in this repository is a proof of concept. I have no intentions of 21 | making it more general (i.e. supporting more devices, probes, architectures, 22 | etc.) than what's necessary for my experiments. That is to say: depend on things 23 | in this repository at your own risk and do not expect any support from me. 24 | 25 | Also, the code in this repository is pretty opinionated; feel free to disagree 26 | with any or all of it. 27 | 28 | Finally, what's written in the READMEs may already be implemented or it may just 29 | be planned. I'm not going to bother to regularly update the status of things in 30 | the READMEs. 31 | 32 | ## Highlights 33 | 34 | ### Code organization 35 | 36 | - The `firmware` folder is a workspace that contains `no_std` crates that will 37 | be compiled for the target. Running any Cargo command within that folder 38 | automatically does cross compilation (see `firmware/.cargo/config`). 39 | 40 | - The `host` folder is a workspace that contains `std` crates that are meant to 41 | be compiled for and executed on the host. These crates can not be cross 42 | compiled to the target (because they depend on `std`). 43 | 44 | - The `shared` folder contains `no_std` crates that either (a) are meant to be 45 | compiled for the target but have been put in this folder so they can be easily 46 | unit tested on the host (`cargo t`) or (b) serve to share data (e.g. 47 | constants) between the firmware and code that will run on the host. 48 | 49 | ### Cargo aliases 50 | 51 | - `.cargo/config` defines a bunch of aliases. `cargo run --release --example 52 | foo` is too long so instead you can type `cargo rre foo`. These aliases are 53 | available in all workspaces. 54 | 55 | ## License 56 | 57 | All source code (including code snippets) is licensed under either of 58 | 59 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 60 | [https://www.apache.org/licenses/LICENSE-2.0][L1]) 61 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or 62 | [https://opensource.org/licenses/MIT][L2]) 63 | 64 | [L1]: https://www.apache.org/licenses/LICENSE-2.0 65 | [L2]: https://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 | licensed as above, without any additional terms or conditions. 74 | -------------------------------------------------------------------------------- /firmware/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabi] 2 | # runner = "semidap -v 0d28 -p 0204" 3 | runner = "semiprobe" 4 | rustflags = [ 5 | "-C", "link-arg=-Tlink.x", 6 | "-C", "linker=flip-lld", 7 | ] 8 | 9 | [build] 10 | target = "thumbv7em-none-eabi" # Cortex-M4F (FPU disabled) 11 | -------------------------------------------------------------------------------- /firmware/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /firmware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "apps", 4 | "asm", 5 | "async-core", 6 | "drivers/mrf24j40", 7 | "executor", 8 | "hal", 9 | "pac", 10 | "panic-abort", 11 | "pool", 12 | "ring", 13 | "semidap", 14 | "tasks", 15 | "tests", 16 | ] 17 | 18 | [profile.dev] 19 | codegen-units = 1 20 | debug = 1 21 | debug-assertions = true # ! 22 | incremental = false 23 | lto = "fat" 24 | opt-level = 'z' # ! 25 | overflow-checks = false 26 | 27 | [profile.release] 28 | codegen-units = 1 29 | debug = 1 30 | debug-assertions = false 31 | incremental = false 32 | lto = "fat" 33 | opt-level = 3 34 | overflow-checks = false 35 | 36 | # `syn` and other proc-macro crates take very long to build when optimized 37 | # this disables optimizations for them reducing the time it takes to build the 38 | # whole dependency graph by ~80% 39 | [profile.dev.build-override] 40 | codegen-units = 16 41 | debug = false 42 | debug-assertions = false 43 | incremental = true 44 | opt-level = 0 45 | overflow-checks = false 46 | 47 | [profile.release.build-override] 48 | codegen-units = 16 49 | debug = false 50 | debug-assertions = false 51 | incremental = true 52 | opt-level = 0 53 | overflow-checks = false 54 | -------------------------------------------------------------------------------- /firmware/README.md: -------------------------------------------------------------------------------- 1 | # `firmware` 2 | 3 | Code that's meant to be compiled for the target and not the host. 4 | 5 | ## Highlights 6 | 7 | ### Optimized `dev` builds 8 | 9 | When developing complex programs, unoptitmized builds are usually too large to 10 | fit in the device memory. Here, we optimize `dev` builds for size: this 11 | minimizes program loading times during development (smaller binaries can be 12 | loaded faster). Optimizing for size always does way less aggressive inlining; 13 | this produces more useful stack backtraces. 14 | 15 | ### Unoptimized build dependencies 16 | 17 | Build dependencies, dependencies used in procedural macros and build scripts, 18 | are build without optimizations, without debug info, incrementally and with 19 | multiple LLVM codegen units. These dependencies won't be part of the device 20 | program so it's not important that they are fast or small. Compiling build 21 | dependencies with these settings significantly reduces compilation times when 22 | building from scratch (e.g. after `cargo clean`). 23 | 24 | ### Stack overflow protection 25 | 26 | Zero-cost stack overflow protection, as described in [this blog post], is 27 | enabled by default. The `flip-lld` linker wrapper takes care of inverting the 28 | memory layout of the program by invoking the linker ~twice~ as many times as 29 | necessary. 30 | 31 | [this blog post]: https://blog.japaric.io/stack-overflow-protection/ 32 | 33 | ## Running the examples 34 | 35 | Before you can run the examples you'll need the perform the following one-time 36 | setup. 37 | 38 | ### One-time setup 39 | 40 | - `rustup target add thumbv7em-none-eabi`, cross compilation support 41 | 42 | - `cargo install --git https://github.com/japaric/flip-lld`, linker wrapper that 43 | adds stack overflow protection 44 | 45 | - `cd ../host && cargo install --path semidap`, tool to run embedded 46 | applications as if they were native applications 47 | 48 | ### Per debug session setup 49 | 50 | - Connect the nRF52840 MDK to your PC using a USB-C cable. 51 | 52 | ### Per example steps 53 | 54 | Just run 55 | 56 | ``` console 57 | $ # optional 58 | $ export RUST_LOG=semidap=info 59 | 60 | $ # or using the rb alias: `cargo rb led` 61 | $ cargo r --bin hello 62 | Finished dev [optimized + debuginfo] target(s) in 0.02s 63 | Running `semidap -v 0d28 -p 0204 target/thumbv7em-none-eabi/debug/hello` 64 | [2020-05-06T21:58:52Z INFO semidap] DAP S/N: 1026000013ac88bc00000000000000000000000097969902 65 | [2020-05-06T21:58:52Z INFO semidap] target: ARM Cortex-M4 (CPUID = 0x410fc241) 66 | [2020-05-06T21:58:52Z INFO semidap] loaded `.text` (552 B) in 21.86605ms 67 | [2020-05-06T21:58:52Z INFO semidap] loaded `.bss` (4 B) in 3.949307ms 68 | [2020-05-06T21:58:52Z INFO semidap] loaded `.vectors` (256 B) in 12.030267ms 69 | [2020-05-06T21:58:52Z INFO semidap] loaded 812 bytes in 38.008952ms (21363 B/s) 70 | [2020-05-06T21:58:52Z INFO semidap] booting program (start to end: 77.919153ms) 71 | 0> 0.000_001s INFO Hello, world! 72 | ``` 73 | -------------------------------------------------------------------------------- /firmware/apps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "apps" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [[bin]] 10 | name = "acm" 11 | required-features = ["usb"] 12 | 13 | [[bin]] 14 | name = "radio" 15 | required-features = ["radio"] 16 | 17 | [[bin]] 18 | name = "loopback" 19 | required-features = ["hid", "radio"] 20 | 21 | [dependencies] 22 | asm = { path = "../asm" } 23 | async-core = { path = "../async-core" } 24 | binfmt = { path = "../../shared/binfmt" } 25 | executor = { path = "../executor" } 26 | hal = { path = "../hal" } 27 | heapless = "0.5.5" 28 | panic-abort = { path = "../panic-abort" } 29 | panic-never = "0.1.0" 30 | semidap = { path = "../semidap" } 31 | 32 | [features] 33 | radio = ["hal/radio"] 34 | usb = ["hal/usb"] 35 | hid = ["hal/hid", "usb"] 36 | -------------------------------------------------------------------------------- /firmware/apps/README.md: -------------------------------------------------------------------------------- 1 | # `apps` 2 | 3 | > Example applications 4 | 5 | ## Highlights 6 | 7 | Applications never depend on the PAC; they only use the HAL and higher level 8 | abstractions. 9 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/abort.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use hal as _; // memory layout 5 | use panic_never as _; // this program contains zero core::panic* calls 6 | 7 | #[no_mangle] 8 | fn main() -> ! { 9 | foo(true); 10 | 11 | semidap::exit(0) 12 | } 13 | 14 | fn foo(recurse: bool) { 15 | let mut x = [0]; 16 | let y = x.as_mut_ptr(); // use the stack 17 | unsafe { 18 | (&y as *const *mut i32).read_volatile(); 19 | } 20 | 21 | if recurse { 22 | foo(false) 23 | } else { 24 | bar() 25 | } 26 | } 27 | 28 | fn bar() { 29 | semidap::abort() 30 | } 31 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/acm.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use core::{fmt::Write as _, time::Duration}; 6 | 7 | use hal::{timer::Timer, usbd}; 8 | use heapless::{consts, String}; 9 | use panic_abort as _; 10 | 11 | #[no_mangle] 12 | fn main() -> ! { 13 | let mut tx = usbd::serial(); 14 | let mut timer = Timer::claim(); 15 | let mut buf = String::::new(); 16 | 17 | let task = async { 18 | let mut i = 0; 19 | loop { 20 | buf.clear(); 21 | writeln!(&mut buf, "Hello {}", i).ok(); 22 | i += 1; 23 | tx.write(buf.as_bytes()); 24 | timer.wait(Duration::from_secs(10)).await; 25 | } 26 | }; 27 | 28 | executor::run!(task) 29 | } 30 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/blinky.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::time::Duration; 5 | 6 | use hal::{led, timer::Timer}; 7 | use panic_never as _; // this program contains zero core::panic* calls 8 | 9 | #[no_mangle] 10 | fn main() -> ! { 11 | let dur = Duration::from_secs(1); 12 | let mut timer = Timer::claim(); 13 | 14 | let blinky = async { 15 | loop { 16 | led::Blue.on(); 17 | timer.wait(dur).await; 18 | 19 | led::Blue.off(); 20 | timer.wait(dur).await; 21 | } 22 | }; 23 | 24 | executor::run!(blinky) 25 | } 26 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/core-panic.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use hal as _; 5 | use panic_abort as _; // panic handler 6 | 7 | #[no_mangle] 8 | fn main() -> ! { 9 | panic!() 10 | } 11 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/deviceid.rs: -------------------------------------------------------------------------------- 1 | //! (test) Stack backtrace across nested exceptions 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use hal as _; // memory layout 7 | use panic_never as _; // this program contains zero core::panic* calls 8 | 9 | #[no_mangle] 10 | fn main() -> ! { 11 | semidap::info!("I am {}", hal::deviceid().to_le_bytes()[..]); 12 | 13 | semidap::exit(0) 14 | } 15 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/fmt.rs: -------------------------------------------------------------------------------- 1 | //! Binary formatting 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use panic_never as _; // this program contains zero core::panic* calls 7 | 8 | #[no_mangle] 9 | fn main() -> ! { 10 | let a = hal::cyccnt(); 11 | 12 | semidap::info!("The answer is {}", 0.12345678); 13 | // ^ this sends the byte sequence: 14 | // [2, 1, 0, 8, 233, 214, 252, 61] 15 | // | | | | /^^^^^^^^^^^^^^^^ 16 | // | | | | +-> `f32.to_le_bytes()` 17 | // | | | +----> TAG_F32 18 | // | | +-------> the footprint (or formatting string) *index* 19 | // | +----------> timestamp in microseconds (LEB128 encoded) 20 | // +-------------> TAG_INFO 21 | 22 | let b = hal::cyccnt(); 23 | 24 | semidap::info!("That took {} cycles", b - a); 25 | 26 | semidap::exit(0) 27 | } 28 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/hard-fault.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use hal as _; // memory layout 5 | use panic_never as _; // this program contains zero core::panic* calls 6 | 7 | #[no_mangle] 8 | fn main() -> ! { 9 | // this tries to read non-existent memory and causes a 10 | // `HardFault` (hardware) exception 11 | unsafe { 12 | (0xffff_fff0 as *const u32).read_volatile(); 13 | } 14 | 15 | semidap::exit(0); 16 | } 17 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/heartbeat.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use hal as _; // memory layout 5 | use panic_never as _; // this program contains zero core::panic* calls 6 | 7 | // the heartbeat task runs in the background so we just sleep here 8 | #[no_mangle] 9 | fn main() -> ! { 10 | loop { 11 | asm::wfi() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/hello.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use hal as _; // memory layout 5 | use panic_never as _; // this program contains zero core::panic* calls 6 | 7 | #[no_mangle] 8 | fn main() -> ! { 9 | // This operation does NOT halt the device 10 | semidap::info!("Hello, world!"); 11 | 12 | // This halts the device and terminates the `semidap` instance running 13 | // on the host 14 | semidap::exit(0); 15 | } 16 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/hid.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use hal::usbd::{self, Packet}; 6 | use panic_abort as _; 7 | 8 | #[no_mangle] 9 | fn main() -> ! { 10 | let (mut hidout, mut hidin) = usbd::hid(); 11 | 12 | let task = async { 13 | let mut packet = Packet::new().await; 14 | hidout.recv(&mut packet).await; 15 | hidin.send(&packet).await; 16 | hidin.flush().await; 17 | semidap::exit(0) 18 | }; 19 | 20 | executor::run!(task) 21 | } 22 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/led.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use hal::led; 5 | use panic_never as _; // this program contains zero core::panic* calls 6 | 7 | #[no_mangle] 8 | fn main() -> ! { 9 | led::Blue.on(); 10 | 11 | semidap::exit(0) 12 | } 13 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/log.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::time::Duration; 5 | 6 | use hal::time; 7 | use panic_never as _; // this program contains zero core::panic* calls 8 | 9 | #[no_mangle] 10 | fn main() -> ! { 11 | semidap::info!("Start"); 12 | 13 | semidap::debug!("working.."); 14 | // pretend we are doing some work 15 | while time::uptime() < Duration::from_millis(1) { 16 | continue; 17 | } 18 | 19 | semidap::error!("Something went wrong. Exiting.."); 20 | 21 | semidap::exit(1); 22 | } 23 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/loopback.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use core::{convert::TryFrom, fmt::Write as _}; 6 | 7 | use async_core::unsync::Mutex; 8 | use hal::{ 9 | radio::{self, Channel}, 10 | usbd, 11 | }; 12 | use heapless::{consts, String}; 13 | use panic_abort as _; 14 | 15 | #[no_mangle] 16 | fn main() -> ! { 17 | let stx = Mutex::new(usbd::serial()); 18 | let (mut hidout, _) = usbd::hid(); 19 | let (rtx, mut rrx) = radio::claim(Channel::_20); 20 | 21 | let mut output = String::::new(); 22 | 23 | output.push_str("deviceid=").ok(); 24 | write!(output, "{:08x}{:08x}", hal::deviceid1(), hal::deviceid0()).ok(); 25 | write!( 26 | output, 27 | " channel={} TxPower=+8dBm app=loopback.hex\n", 28 | rtx.channel() 29 | ) 30 | .ok(); 31 | 32 | let rtx = Mutex::new(rtx); 33 | 34 | let t1 = async { 35 | let mut output = String::::new(); 36 | let mut hidbuf = usbd::Packet::new().await; 37 | let zlp = radio::Packet::new().await; 38 | 39 | loop { 40 | hidout.recv(&mut hidbuf).await; 41 | semidap::info!("HID: {}", *hidbuf); 42 | 43 | let arg = if hidbuf.len() == 1 { 44 | // Linux / macOS 45 | Some(hidbuf[0]) 46 | } else if hidbuf.len() == 64 { 47 | // Windows (it zero pads the packet) 48 | Some(hidbuf[0]) 49 | } else { 50 | None 51 | }; 52 | 53 | if let Some(arg) = arg { 54 | if let Ok(chan) = Channel::try_from(arg) { 55 | let mut rtx = rtx.lock().await; 56 | rtx.set_channel(chan); 57 | // send a zero-length packet to force the radio to listen on the new channel 58 | rtx.write(&zlp).await.ok(); 59 | drop(rtx); 60 | 61 | output.clear(); 62 | writeln!(output, "now listening on channel {}", chan).ok(); 63 | stx.lock().await.write(output.as_bytes()); 64 | } else { 65 | stx.lock() 66 | .await 67 | .write(b"requested channel is out of range (11-26)\n"); 68 | } 69 | } else { 70 | stx.lock().await.write(b"invalid HID packet\n"); 71 | } 72 | } 73 | }; 74 | 75 | let t2 = async { 76 | let mut packet = radio::Packet::new().await; 77 | stx.lock().await.write(output.as_bytes()); 78 | 79 | loop { 80 | let crcres = rrx.read(&mut packet).await; 81 | let len = packet.len(); 82 | let lqi = if len >= 3 { 83 | Some(packet.lqi()) 84 | } else { 85 | // packet is too small; LQI is not valid 86 | None 87 | }; 88 | 89 | let mut busy = false; 90 | if crcres.is_ok() { 91 | packet.reverse(); 92 | busy = rtx.lock().await.write(&packet).await.is_err(); 93 | } 94 | 95 | output.clear(); 96 | write!( 97 | &mut output, 98 | "received {} byte{}", 99 | len, 100 | if len == 1 { "" } else { "s" } 101 | ) 102 | .ok(); 103 | 104 | let (res, crc) = match crcres { 105 | Ok(x) => ("Ok", x), 106 | Err(x) => ("Err", x), 107 | }; 108 | 109 | write!(&mut output, " (CRC={}({:#06x})", res, crc).ok(); 110 | if let Some(lqi) = lqi { 111 | write!(&mut output, ", LQI={}", lqi).ok(); 112 | } 113 | output.push_str(")\n").ok(); 114 | 115 | if busy { 116 | output.push_str("didn't reply -- channel was busy\n").ok(); 117 | stx.lock().await.write(output.as_bytes()); 118 | } 119 | 120 | stx.lock().await.write(output.as_bytes()); 121 | } 122 | }; 123 | 124 | executor::run!(t1, t2) 125 | } 126 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/radio.rs: -------------------------------------------------------------------------------- 1 | //! Zero-copy, async IEEE 802.15.4 radio loopback 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use hal::radio::{self, Channel}; 7 | use panic_abort as _; 8 | 9 | #[no_mangle] 10 | fn main() -> ! { 11 | let (mut tx, _) = radio::claim(Channel::_20); 12 | 13 | let task = async { 14 | let mut packet = radio::Packet::new().await; 15 | packet.copy_from_slice(b"hello"); 16 | tx.write(&packet).await.ok(); 17 | semidap::exit(0); 18 | }; 19 | 20 | executor::run!(task) 21 | } 22 | -------------------------------------------------------------------------------- /firmware/apps/src/bin/stack-overflow.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use hal as _; 5 | use panic_never as _; // this program contains zero core::panic* calls 6 | 7 | #[no_mangle] 8 | fn main() -> ! { 9 | fib(15); 10 | 11 | semidap::exit(0); 12 | } 13 | 14 | fn fib(n: u32) -> u32 { 15 | let mut x = [n; 8 * 1024]; // allocate a 32 KB buffer on the stack 16 | semidap::trace!("SP = {}", x.as_mut_ptr()); 17 | 18 | if n < 2 { 19 | 1 20 | } else { 21 | fib(n - 1).wrapping_add(fib(n - 2)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /firmware/asm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "asm" 6 | publish = false 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /firmware/asm/asm.s: -------------------------------------------------------------------------------- 1 | .global __cpsiei 2 | .cfi_sections .debug_frame 3 | .section .text.__cpsiei, "ax" 4 | .thumb_func 5 | .cfi_startproc 6 | __cpsiei: 7 | cpsie i 8 | bx lr 9 | .cfi_endproc 10 | .size __cpsiei, . - __cpsiei 11 | 12 | .global __cpsidi 13 | .cfi_sections .debug_frame 14 | .section .text.__cpsidi, "ax" 15 | .thumb_func 16 | .cfi_startproc 17 | __cpsidi: 18 | cpsid i 19 | bx lr 20 | .cfi_endproc 21 | .size __cpsidi, . - __cpsidi 22 | 23 | .global __sev 24 | .cfi_sections .debug_frame 25 | .section .text.__sev, "ax" 26 | .thumb_func 27 | .cfi_startproc 28 | __sev: 29 | sev 30 | bx lr 31 | .cfi_endproc 32 | .size __sev, . - __sev 33 | 34 | .global __wfe 35 | .cfi_sections .debug_frame 36 | .section .text.__wfe, "ax" 37 | .thumb_func 38 | .cfi_startproc 39 | __wfe: 40 | wfe 41 | bx lr 42 | .cfi_endproc 43 | .size __wfe, . - __wfe 44 | 45 | .global __wfi 46 | .cfi_sections .debug_frame 47 | .section .text.__wfi, "ax" 48 | .thumb_func 49 | .cfi_startproc 50 | __wfi: 51 | wfi 52 | bx lr 53 | .cfi_endproc 54 | .size __wfi, . - __wfi 55 | -------------------------------------------------------------------------------- /firmware/asm/assemble.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | local pkg_name=asm 5 | 6 | arm-none-eabi-as -march=armv7e-m asm.s -o bin/$pkg_name.o 7 | ar crs bin/thumbv7em-none-eabi.a bin/$pkg_name.o 8 | 9 | rm bin/*.o 10 | } 11 | 12 | main 13 | -------------------------------------------------------------------------------- /firmware/asm/bin/thumbv7em-none-eabi.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japaric/embedded2020/da43017e25ea2d3df11ec1e4ecf8e77d60868038/firmware/asm/bin/thumbv7em-none-eabi.a -------------------------------------------------------------------------------- /firmware/asm/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let out_dir = &PathBuf::from(env::var("OUT_DIR")?); 5 | let pkg_name = env::var("CARGO_PKG_NAME")?; 6 | let target = env::var("TARGET")?; 7 | 8 | // place the pre-compiled assembly somewhere the linker can find it 9 | if target.starts_with("thumb") { 10 | fs::copy( 11 | format!("bin/{}.a", target), 12 | out_dir.join(format!("lib{}.a", pkg_name)), 13 | )?; 14 | println!("cargo:rustc-link-lib=static={}", pkg_name); 15 | 16 | println!("cargo:rustc-link-search={}", out_dir.display()); 17 | } 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /firmware/asm/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Cortex-M assembly 2 | //! 3 | //! `cortex_m::asm` module but with CFI and size information 4 | 5 | #![deny(missing_docs)] 6 | #![deny(warnings)] 7 | #![no_std] 8 | 9 | /// Masks interrupts 10 | pub fn disable_irq() { 11 | extern "C" { 12 | fn __cpsidi(); 13 | } 14 | unsafe { __cpsidi() } 15 | } 16 | 17 | /// Unmasks interrupts 18 | pub fn enable_irq() { 19 | extern "C" { 20 | fn __cpsiei(); 21 | } 22 | unsafe { __cpsiei() } 23 | } 24 | 25 | /// Send EVent 26 | pub fn sev() { 27 | #[cfg(target_arch = "arm")] 28 | extern "C" { 29 | fn __sev(); 30 | } 31 | #[cfg(target_arch = "arm")] 32 | unsafe { 33 | __sev() 34 | } 35 | } 36 | 37 | /// Wait For Event 38 | pub fn wfe() { 39 | extern "C" { 40 | fn __wfe(); 41 | } 42 | unsafe { __wfe() } 43 | } 44 | 45 | /// Wait For Interrupt 46 | pub fn wfi() { 47 | extern "C" { 48 | fn __wfi(); 49 | } 50 | unsafe { __wfi() } 51 | } 52 | -------------------------------------------------------------------------------- /firmware/async-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "async-core" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | asm = { path = "../asm" } 11 | -------------------------------------------------------------------------------- /firmware/async-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `async` primitives 2 | 3 | #![deny(missing_docs)] 4 | #![deny(rust_2018_compatibility)] 5 | #![deny(rust_2018_idioms)] 6 | #![deny(unused_qualifications)] 7 | #![deny(warnings)] 8 | #![no_std] 9 | 10 | pub mod task; 11 | pub mod unsync; 12 | -------------------------------------------------------------------------------- /firmware/async-core/src/task.rs: -------------------------------------------------------------------------------- 1 | //! Tasks 2 | 3 | use core::{ 4 | future::Future, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | struct Yield { 10 | yielded: bool, 11 | } 12 | 13 | impl Future for Yield { 14 | type Output = (); 15 | 16 | fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> { 17 | if self.yielded { 18 | Poll::Ready(()) 19 | } else { 20 | self.yielded = true; 21 | // wake ourselves 22 | asm::sev(); 23 | 24 | Poll::Pending 25 | } 26 | } 27 | } 28 | 29 | /// Suspends the current task 30 | pub fn r#yield() -> impl Future { 31 | Yield { yielded: false } 32 | } 33 | -------------------------------------------------------------------------------- /firmware/async-core/src/unsync.rs: -------------------------------------------------------------------------------- 1 | //! `!Sync` task synchronization primitives 2 | 3 | mod mutex; 4 | pub mod spsc; 5 | 6 | pub use mutex::Mutex; 7 | -------------------------------------------------------------------------------- /firmware/async-core/src/unsync/mutex.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | cell::{Cell, UnsafeCell}, 3 | future::Future, 4 | ops, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | /// `async`-aware `Mutex` 10 | pub struct Mutex { 11 | data: UnsafeCell, 12 | locked: Cell, 13 | } 14 | 15 | impl Mutex { 16 | /// Creates a new mutex 17 | pub const fn new(data: T) -> Self { 18 | Self { 19 | data: UnsafeCell::new(data), 20 | locked: Cell::new(false), 21 | } 22 | } 23 | 24 | /// Attempts to acquire the lock 25 | pub fn try_lock(&self) -> Option> { 26 | if self.locked.get() { 27 | None 28 | } else { 29 | self.locked.set(true); 30 | Some(MutexGuard { mutex: self }) 31 | } 32 | } 33 | 34 | /// Acquires the lock 35 | pub fn lock(&self) -> impl Future> { 36 | struct Lock<'a, T> { 37 | mutex: &'a Mutex, 38 | } 39 | 40 | impl<'m, T> Future for Lock<'m, T> { 41 | type Output = MutexGuard<'m, T>; 42 | 43 | fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { 44 | if let Some(guard) = self.mutex.try_lock() { 45 | Poll::Ready(guard) 46 | } else { 47 | Poll::Pending 48 | } 49 | } 50 | } 51 | 52 | Lock { mutex: self } 53 | } 54 | } 55 | 56 | /// A locked mutex 57 | pub struct MutexGuard<'m, T> { 58 | mutex: &'m Mutex, 59 | } 60 | 61 | impl ops::Deref for MutexGuard<'_, T> { 62 | type Target = T; 63 | 64 | fn deref(&self) -> &T { 65 | unsafe { &*self.mutex.data.get() } 66 | } 67 | } 68 | 69 | impl ops::DerefMut for MutexGuard<'_, T> { 70 | fn deref_mut(&mut self) -> &mut T { 71 | unsafe { &mut *self.mutex.data.get() } 72 | } 73 | } 74 | 75 | impl Drop for MutexGuard<'_, T> { 76 | fn drop(&mut self) { 77 | self.mutex.locked.set(false); 78 | 79 | // wake up a task waiting to claim this mutex 80 | asm::sev(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /firmware/async-core/src/unsync/spsc.rs: -------------------------------------------------------------------------------- 1 | //! Single Producer Single Consumer channels 2 | 3 | use core::{ 4 | cell::{Cell, UnsafeCell}, 5 | future::Future, 6 | mem::{ManuallyDrop, MaybeUninit}, 7 | pin::Pin, 8 | ptr, 9 | task::{Context, Poll}, 10 | }; 11 | 12 | /// `async`-aware channel 13 | // TODO user configurable capacity 14 | pub struct Channel { 15 | buffer: UnsafeCell>, 16 | full: Cell, 17 | } 18 | 19 | impl Channel { 20 | /// Creates a new channel 21 | pub const fn new() -> Self { 22 | Self { 23 | buffer: UnsafeCell::new(MaybeUninit::uninit()), 24 | full: Cell::new(false), 25 | } 26 | } 27 | 28 | /// Splits the channel in `sender` and `receiver` endpoints 29 | pub fn split(&mut self) -> (Sender<'_, T>, Receiver<'_, T>) { 30 | let channel = self; 31 | (Sender { channel }, Receiver { channel }) 32 | } 33 | } 34 | 35 | /// Sending side of a channel 36 | pub struct Sender<'c, T> { 37 | channel: &'c Channel, 38 | } 39 | 40 | impl Sender<'_, T> { 41 | /// Sends a message into the channel 42 | pub fn send<'s>(&'s mut self, msg: T) -> impl Future + 's { 43 | struct Send<'s, 'c, T> { 44 | msg: ManuallyDrop, 45 | sender: &'s Sender<'c, T>, 46 | sent: Cell, 47 | } 48 | 49 | impl Future for Send<'_, '_, T> { 50 | type Output = (); 51 | 52 | fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> { 53 | if !self.sender.channel.full.get() { 54 | let bufferp = self.sender.channel.buffer.get() as *mut T; 55 | unsafe { bufferp.write(ptr::read(&*self.msg)) } 56 | 57 | self.sent.set(true); 58 | self.sender.channel.full.set(true); 59 | 60 | // wake up the receiver 61 | asm::sev(); 62 | 63 | Poll::Ready(()) 64 | } else { 65 | Poll::Pending 66 | } 67 | } 68 | } 69 | 70 | impl Drop for Send<'_, '_, T> { 71 | fn drop(&mut self) { 72 | if !self.sent.get() { 73 | unsafe { ManuallyDrop::drop(&mut self.msg) } 74 | } 75 | } 76 | } 77 | 78 | Send { 79 | msg: ManuallyDrop::new(msg), 80 | sender: self, 81 | sent: Cell::new(false), 82 | } 83 | } 84 | } 85 | 86 | /// The receiving side of a channel 87 | pub struct Receiver<'c, T> { 88 | channel: &'c Channel, 89 | } 90 | 91 | impl Receiver<'_, T> { 92 | /// Receives a message from the channel 93 | pub fn recv<'r>(&'r mut self) -> impl Future + 'r { 94 | struct Recv<'r, 'c, T> { 95 | receiver: &'r Receiver<'c, T>, 96 | } 97 | 98 | impl Future for Recv<'_, '_, T> { 99 | type Output = T; 100 | 101 | fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { 102 | if self.receiver.channel.full.get() { 103 | self.receiver.channel.full.set(false); 104 | 105 | let bufferp = self.receiver.channel.buffer.get() as *mut T; 106 | let val = unsafe { bufferp.read() }; 107 | 108 | // wake up the sender 109 | asm::sev(); 110 | 111 | Poll::Ready(val) 112 | } else { 113 | Poll::Pending 114 | } 115 | } 116 | } 117 | 118 | Recv { receiver: self } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /firmware/drivers/mrf24j40/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "mrf24j40" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | binfmt = { path = "../../../shared/binfmt" } 9 | hal = { path = "../../hal" } 10 | semidap = { path = "../../semidap" } 11 | -------------------------------------------------------------------------------- /firmware/drivers/mrf24j40/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | #![deny(unused_result)] 3 | #![no_std] 4 | 5 | use core::{future::Future, mem::MaybeUninit, time::Duration}; 6 | 7 | use hal::{ 8 | spi::{ChipSelect, Spi}, 9 | timer::Timer, 10 | }; 11 | 12 | mod long; 13 | mod reg; 14 | mod short; 15 | 16 | pub struct Mrf24j40 { 17 | spi: Spi, 18 | cs: ChipSelect, 19 | } 20 | 21 | #[derive(Clone, Copy)] 22 | pub enum Channel { 23 | /// `2_405` MHz 24 | _11 = 0, 25 | /// `2_410` MHz 26 | _12 = 1, 27 | /// `2_415` MHz 28 | _13 = 2, 29 | /// `2_420` MHz 30 | _14 = 3, 31 | /// `2_425` MHz 32 | _15 = 4, 33 | /// `2_430` MHz 34 | _16 = 5, 35 | /// `2_435` MHz 36 | _17 = 6, 37 | /// `2_440` MHz 38 | _18 = 7, 39 | /// `2_445` MHz 40 | _19 = 8, 41 | /// `2_450` MHz 42 | _20 = 9, 43 | /// `2_455` MHz 44 | _21 = 10, 45 | /// `2_460` MHz 46 | _22 = 11, 47 | /// `2_465` MHz 48 | _23 = 12, 49 | /// `2_470` MHz 50 | _24 = 13, 51 | /// `2_475` MHz 52 | _25 = 14, 53 | /// `2_480` MHz 54 | _26 = 15, 55 | } 56 | 57 | impl Mrf24j40 { 58 | pub async fn new(spi: Spi, cs: ChipSelect, timer: &mut Timer, channel: Channel) -> Self { 59 | let mut this = Self { spi, cs }; 60 | 61 | /* Initialization as per "Example 3-1" in the data sheet */ 62 | 63 | // FIFOEN = 1, TXONTS = 0x6 64 | this.write_register(reg::PACON2, 0x98).await; 65 | 66 | // RFSTBL = 0x9 67 | this.write_register(reg::TXSTBL, 0x95).await; 68 | 69 | // RFOPT = 0x03 70 | this.write_register(reg::RFCON0, 0x03).await; 71 | 72 | // VCOOPT = 0x02 (NOTE "Example 3-1" says RFCON1 = 0x1, but data sheet says that 0x2 is the 73 | // optimal value) 74 | this.write_register(reg::RFCON1, 0x02).await; 75 | 76 | // Enable PLL (PLLEN = 1) 77 | this.write_register(reg::RFCON2, 0x80).await; 78 | 79 | // TXFIL = 1, 20MRECVR = 1 80 | this.write_register(reg::RFCON6, 0x90).await; 81 | 82 | // SLPCLKSEL = 0x2 (100 KHz internal oscillator) 83 | this.write_register(reg::RFCON7, 0x80).await; 84 | 85 | // RFVCO = 1 86 | this.write_register(reg::RFCON8, 0x10).await; 87 | 88 | // CLKOUTEN = 1, SLPCLKDIV = 0x01 89 | this.write_register(reg::SLPCON1, 0x21).await; 90 | 91 | // the default is 'only carrier sense' 92 | // CCAMODE = ED (0b10) 93 | // this.write_register(reg::BBREG2, 0x80).await; 94 | 95 | // set CCA ED threshold to the recommended value 96 | // this.write_register(reg::CCAEDTH, 0x60).await; 97 | 98 | // append RSSI value to RXFIFO 99 | this.write_register(reg::BBREG6, 0x40).await; 100 | 101 | this.write_register(reg::RFCON0, ((channel as u8) << 4) | 0x03) 102 | .await; 103 | 104 | // Reset RF state machine 105 | this.write_register(reg::RFCTL, 0x04).await; 106 | this.write_register(reg::RFCTL, 0x00).await; 107 | timer.wait(Duration::from_micros(192)); 108 | 109 | this 110 | } 111 | 112 | async fn long_read_register(&mut self, reg: long::Register) -> u8 { 113 | let mut val: [u8; 1] = unsafe { MaybeUninit::uninit().assume_init() }; 114 | self.cs.select(); 115 | self.spi.write(®.opcode(Action::Read)).await; 116 | self.spi.read(&mut val).await; 117 | self.cs.deselect(); 118 | val[0] 119 | } 120 | 121 | async fn long_write_register(&mut self, reg: long::Register, val: u8) { 122 | let opcode = reg.opcode(Action::Write); 123 | self.cs.select(); 124 | self.spi.write(&[opcode[0], opcode[1], val]).await; 125 | self.cs.deselect(); 126 | } 127 | 128 | async fn read_register(&mut self, reg: impl Into) -> u8 { 129 | match reg.into() { 130 | Register::Short(reg) => self.short_read_register(reg).await, 131 | Register::Long(reg) => self.long_read_register(reg).await, 132 | } 133 | } 134 | 135 | async fn write_register(&mut self, reg: impl Into, val: u8) { 136 | match reg.into() { 137 | Register::Short(reg) => self.short_write_register(reg, val).await, 138 | Register::Long(reg) => self.long_write_register(reg, val).await, 139 | } 140 | } 141 | 142 | async fn short_read_register(&mut self, reg: short::Register) -> u8 { 143 | let mut val: [u8; 1] = unsafe { MaybeUninit::uninit().assume_init() }; 144 | self.cs.select(); 145 | self.spi.write(&[reg.opcode(Action::Read)]).await; 146 | self.spi.read(&mut val).await; 147 | self.cs.deselect(); 148 | val[0] 149 | } 150 | 151 | async fn short_write_register(&mut self, reg: short::Register, val: u8) { 152 | self.cs.select(); 153 | self.spi.write(&[reg.opcode(Action::Write), val]).await; 154 | self.cs.deselect(); 155 | } 156 | } 157 | 158 | pub enum Register { 159 | Short(short::Register), 160 | Long(long::Register), 161 | } 162 | 163 | impl From for Register { 164 | fn from(sr: short::Register) -> Self { 165 | Register::Short(sr) 166 | } 167 | } 168 | 169 | impl From for Register { 170 | fn from(sr: long::Register) -> Self { 171 | Register::Long(sr) 172 | } 173 | } 174 | 175 | pub enum Error {} 176 | 177 | enum Action { 178 | Read = 0, 179 | Write = 1, 180 | } 181 | -------------------------------------------------------------------------------- /firmware/drivers/mrf24j40/src/long.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy)] 2 | pub enum Register { 3 | // RF control 4 | RFCON0 = 0x200, 5 | RFCON1 = 0x201, 6 | RFCON2 = 0x202, 7 | RFCON3 = 0x203, 8 | RFCON5 = 0x205, 9 | RFCON6 = 0x206, 10 | RFCON7 = 0x207, 11 | RFCON8 = 0x208, 12 | 13 | // Sleep calibration 14 | SLPCAL0 = 0x209, 15 | SLPCAL1 = 0x20A, 16 | SLPCAL2 = 0x20B, 17 | 18 | // RF state 19 | RFSTATE = 0x20F, 20 | 21 | // Averaged RSSI value 22 | RSSI = 0x210, 23 | 24 | // Sleep clock control 25 | SLPCON0 = 0x211, 26 | SLPCON1 = 0x220, 27 | 28 | // Wake-up time match value 29 | WAKETIMEL = 0x222, 30 | WAKETIMEH = 0x223, 31 | 32 | // Remain counter 33 | REMCNTL = 0x224, 34 | REMCNTH = 0x225, 35 | 36 | // Main counter 37 | MAINCNT0 = 0x226, 38 | MAINCNT1 = 0x227, 39 | MAINCNT2 = 0x228, 40 | MAINCNT3 = 0x229, 41 | 42 | // Test mode 43 | TESTMODE = 0x22F, 44 | 45 | // Associated coordinator extended address 46 | ASSOEADR0 = 0x230, 47 | ASSOEADR1 = 0x231, 48 | ASSOEADR2 = 0x232, 49 | ASSOEADR3 = 0x233, 50 | ASSOEADR4 = 0x234, 51 | ASSOEADR5 = 0x235, 52 | ASSOEADR6 = 0x236, 53 | ASSOEADR7 = 0x237, 54 | 55 | // Associated coordinator short address 56 | ASSOSADR0 = 0x238, 57 | ASSOSADR1 = 0x239, 58 | 59 | // Upper nonce security 60 | UPNONCE0 = 0x240, 61 | UPNONCE1 = 0x241, 62 | UPNONCE2 = 0x242, 63 | UPNONCE3 = 0x243, 64 | UPNONCE4 = 0x244, 65 | UPNONCE5 = 0x245, 66 | UPNONCE6 = 0x246, 67 | UPNONCE7 = 0x247, 68 | UPNONCE8 = 0x248, 69 | UPNONCE9 = 0x249, 70 | UPNONCE10 = 0x24A, 71 | UPNONCE11 = 0x24B, 72 | UPNONCE12 = 0x24C, 73 | } 74 | 75 | impl Register { 76 | pub(crate) fn opcode(self, act: crate::Action) -> [u8; 2] { 77 | (((((1 << 10) | self as u16) << 1) | (act as u16)) << 4).to_be_bytes() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /firmware/drivers/mrf24j40/src/reg.rs: -------------------------------------------------------------------------------- 1 | pub use crate::long::Register::*; 2 | pub use crate::short::Register::*; 3 | -------------------------------------------------------------------------------- /firmware/drivers/mrf24j40/src/short.rs: -------------------------------------------------------------------------------- 1 | use crate::Action; 2 | 3 | #[derive(Clone, Copy)] 4 | pub enum Register { 5 | // Receive MAC control 6 | RXMCR = 0x00, 7 | 8 | // PAN ID 9 | PANIDL = 0x01, 10 | PANIDH = 0x02, 11 | 12 | // Short Address 13 | SADRL = 0x03, 14 | SADRH = 0x04, 15 | 16 | // 64-bit extended address 17 | EADR0 = 0x05, 18 | EADR1 = 0x06, 19 | EADR2 = 0x07, 20 | EADR3 = 0x08, 21 | EADR4 = 0x09, 22 | EADR5 = 0x0A, 23 | EADR6 = 0x0B, 24 | EADR7 = 0x0C, 25 | 26 | // Receive FIFO flush 27 | RXFLUSH = 0x0D, 28 | 29 | // Beacon and superframe order 30 | ORDER = 0x10, 31 | 32 | // CSMA-CA mode control 33 | TXMCR = 0x11, 34 | 35 | // MAC ACK time-out duration 36 | ACKTMOUT = 0x12, 37 | 38 | // GTS1 and cap end slot 39 | ESLOTG1 = 0x13, 40 | 41 | // Symbol period tick 42 | SYMTICKL = 0x14, 43 | SYMTICKH = 0x15, 44 | 45 | // Power amplifier control 46 | PACON0 = 0x16, 47 | PACON1 = 0x17, 48 | PACON2 = 0x18, 49 | 50 | // Transmit beacon FIFO control 0 51 | TXBCON0 = 0x1A, 52 | 53 | // Transmit normal FIFO control 54 | TXNCON = 0x1B, 55 | 56 | // GTS1 FIFO control 57 | TXG1CON = 0x1C, 58 | 59 | // GTS2 FIFO control 60 | TXG2CON = 0x1D, 61 | 62 | // End slot of GTS3 and GTS2 63 | ESLOTG23 = 0x1E, 64 | 65 | // End slot of GTS5 and GTS4 66 | ESLOTG45 = 0x1F, 67 | 68 | // End slot of GTS6 69 | ESLOTG67 = 0x20, 70 | 71 | // TX data pending 72 | TXPEND = 0x21, 73 | 74 | // Wake control 75 | WAKECON = 0x22, 76 | 77 | // Superframe counter offset to align beacon 78 | FRMOFFSET = 0x23, 79 | 80 | // TX MAC status 81 | TXSTAT = 0x24, 82 | 83 | // Transmit beacon FIFO control 1 84 | TXBCON1 = 0x25, 85 | 86 | // Gated clock control 87 | GATECLK = 0x26, 88 | 89 | // TX turnaround time 90 | TXTIME = 0x27, 91 | 92 | // Half symbol timer 93 | HSYMTMRL = 0x28, 94 | HSYMTMRH = 0x29, 95 | 96 | // Software reset 97 | SOFTRST = 0x2A, 98 | 99 | // Security control 100 | SECCON0 = 0x2C, 101 | SECCON1 = 0x2D, 102 | 103 | // TX stabilization 104 | TXSTBL = 0x2E, 105 | 106 | // RX MAC status 107 | RXSR = 0x30, 108 | 109 | // Interrupt status 110 | INTSTAT = 0x31, 111 | 112 | // Interrupt control 113 | INTCON = 0x32, 114 | 115 | // GPIO port 116 | GPIO = 0x33, 117 | 118 | // GPIO pin direction 119 | TRISGPIO = 0x34, 120 | 121 | // Sleep acknowledgment and wake-up counter 122 | SLPACK = 0x35, 123 | 124 | // RF mode control 125 | RFCTL = 0x36, 126 | 127 | // Security control 2 128 | SECCR2 = 0x37, 129 | 130 | // Baseband 131 | BBREG0 = 0x38, 132 | BBREG1 = 0x39, 133 | BBREG2 = 0x3A, 134 | BBREG3 = 0x3B, 135 | BBREG4 = 0x3C, 136 | BBREG6 = 0x3E, 137 | 138 | // Energy detection for CCA 139 | CCAEDTH = 0x3F, 140 | } 141 | 142 | impl Register { 143 | pub(crate) fn opcode(self, action: Action) -> u8 { 144 | ((self as u8) << 1) | action as u8 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /firmware/executor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "executor" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | asm = { path = "../asm" } 11 | executor-macros = { path = "../../host/executor-macros" } 12 | proc-macro-hack = "0.5.12" 13 | proc-macro-nested = "0.1.4" 14 | -------------------------------------------------------------------------------- /firmware/executor/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ARM Cortex-M executor 2 | //! 3 | //! # Features 4 | //! 5 | //! - No heap allocations 6 | //! - No trait objects 7 | //! - Tasks do NOT need to satisfy the `: 'static` bound 8 | 9 | #![deny(missing_docs)] 10 | #![deny(rust_2018_idioms)] 11 | #![deny(warnings)] 12 | #![no_std] 13 | 14 | use core::{ 15 | future::Future, 16 | task::{RawWaker, RawWakerVTable, Waker}, 17 | }; 18 | 19 | use proc_macro_hack::proc_macro_hack; 20 | 21 | /// Implementation detail 22 | #[doc(hidden)] 23 | pub use asm::wfe; 24 | 25 | /// Runs the given tasks concurrently 26 | /// 27 | /// This macro is divergent (`-> !`); the tasks should also be divergent 28 | #[proc_macro_hack(support_nested)] 29 | pub use executor_macros::run; 30 | 31 | /// Implementation detail 32 | #[doc(hidden)] 33 | #[inline(always)] 34 | pub fn check(f: F) -> F 35 | where 36 | F: Future, 37 | { 38 | f 39 | } 40 | 41 | /// Implementation detail 42 | #[doc(hidden)] 43 | #[inline(always)] 44 | pub fn waker() -> Waker { 45 | unsafe fn clone(_: *const ()) -> RawWaker { 46 | loop { 47 | continue; 48 | } 49 | } 50 | 51 | unsafe fn wake(_: *const ()) {} 52 | unsafe fn wake_by_ref(_: *const ()) {} 53 | unsafe fn drop(_: *const ()) {} 54 | 55 | static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); 56 | 57 | unsafe { Waker::from_raw(RawWaker::new(&(), &VTABLE)) } 58 | } 59 | -------------------------------------------------------------------------------- /firmware/hal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "hal" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [build-dependencies] 10 | consts = { path = "../../shared/consts" } 11 | quote = "1" 12 | usb2 = { git = "https://github.com/japaric/usb2" } 13 | 14 | [dependencies] 15 | asm = { path = "../asm" } 16 | binfmt = { path = "../../shared/binfmt" } 17 | consts = { path = "../../shared/consts" } 18 | pool = { path = "../pool" } 19 | ring = { path = "../ring" } 20 | semidap = { path = "../semidap" } 21 | tasks = { path = "../tasks" } 22 | usb2 = { git = "https://github.com/japaric/usb2" } 23 | 24 | [dependencies.cm] 25 | features = ["DCB", "DWT", "NVIC"] 26 | path = "../../shared/cm" 27 | 28 | [dependencies.pac] 29 | features = ["binfmt", "CLOCK", "FICR", "P0", "RTC0", "SPIM0"] 30 | path = "../pac" 31 | 32 | [features] 33 | flash = [] 34 | hid = ["usb"] 35 | radio = ["pac/RADIO"] 36 | usb = ["pac/POWER", "pac/USBD"] -------------------------------------------------------------------------------- /firmware/hal/README.md: -------------------------------------------------------------------------------- 1 | # `hal` 2 | 3 | > Hardware Abstraction Layer 4 | 5 | ## Highlights 6 | 7 | ### `hal::time::Instant` 8 | 9 | A monotonically nondecreasing timer is configured and started before `main`. 10 | `hal::time::Instant` inter-operates with `core::time::Duration`, which uses 11 | human-friendly units of time (i.e. seconds). 12 | 13 | This API is indispensable for any development as it lets the developer insert 14 | timeouts when checking for changes in status flags. It's easy to get these 15 | checks wrong when first reading the reference manual and without a timeout 16 | mechanism one may end up using unbound `while` loops, which can leave the 17 | program stuck in an infinite loop. 18 | -------------------------------------------------------------------------------- /firmware/hal/interrupts.x: -------------------------------------------------------------------------------- 1 | /* Weak exceptions */ 2 | Reserved = 0; 3 | EXTERN(VECTORS); 4 | EXTERN(DefaultHandler); 5 | PROVIDE(NMI = DefaultHandler); 6 | PROVIDE(HardFault = DefaultHandler); 7 | PROVIDE(MemManage = DefaultHandler); 8 | PROVIDE(BusFault = DefaultHandler); 9 | PROVIDE(UsageFault = DefaultHandler); 10 | PROVIDE(SVCall = DefaultHandler); 11 | PROVIDE(DebugMonitor = DefaultHandler); 12 | PROVIDE(PendSV = DefaultHandler); 13 | PROVIDE(SysTick = DefaultHandler); 14 | 15 | /* Weak interrupts */ 16 | PROVIDE(POWER_CLOCK = DefaultHandler); 17 | PROVIDE(POWER = DefaultHandler); 18 | PROVIDE(CLOCK = DefaultHandler); 19 | PROVIDE(RADIO = DefaultHandler); 20 | PROVIDE(UARTE0_UART0 = DefaultHandler); 21 | PROVIDE(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 = DefaultHandler); 22 | PROVIDE(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 = DefaultHandler); 23 | PROVIDE(NFCT = DefaultHandler); 24 | PROVIDE(GPIOTE = DefaultHandler); 25 | PROVIDE(SAADC = DefaultHandler); 26 | PROVIDE(TIMER0 = DefaultHandler); 27 | PROVIDE(TIMER1 = DefaultHandler); 28 | PROVIDE(TIMER2 = DefaultHandler); 29 | PROVIDE(RTC0 = DefaultHandler); 30 | PROVIDE(TEMP = DefaultHandler); 31 | PROVIDE(RNG = DefaultHandler); 32 | PROVIDE(ECB = DefaultHandler); 33 | PROVIDE(CCM_AAR = DefaultHandler); 34 | PROVIDE(WDT = DefaultHandler); 35 | PROVIDE(RTC1 = DefaultHandler); 36 | PROVIDE(QDEC = DefaultHandler); 37 | PROVIDE(COMP_LPCOMP = DefaultHandler); 38 | PROVIDE(SWI0_EGU0 = DefaultHandler); 39 | PROVIDE(SWI1_EGU1 = DefaultHandler); 40 | PROVIDE(SWI2_EGU2 = DefaultHandler); 41 | PROVIDE(SWI3_EGU3 = DefaultHandler); 42 | PROVIDE(SWI4_EGU4 = DefaultHandler); 43 | PROVIDE(SWI5_EGU5 = DefaultHandler); 44 | PROVIDE(TIMER3 = DefaultHandler); 45 | PROVIDE(TIMER4 = DefaultHandler); 46 | PROVIDE(PWM0 = DefaultHandler); 47 | PROVIDE(PDM = DefaultHandler); 48 | PROVIDE(MWU = DefaultHandler); 49 | PROVIDE(PWM1 = DefaultHandler); 50 | PROVIDE(PWM2 = DefaultHandler); 51 | PROVIDE(SPIM2_SPIS2_SPI2 = DefaultHandler); 52 | PROVIDE(RTC2 = DefaultHandler); 53 | PROVIDE(I2S = DefaultHandler); 54 | PROVIDE(FPU = DefaultHandler); 55 | PROVIDE(USBD = DefaultHandler); 56 | PROVIDE(UARTE1 = DefaultHandler); 57 | PROVIDE(QSPI = DefaultHandler); 58 | PROVIDE(CRYPTOCELL = DefaultHandler); 59 | PROVIDE(PWM3 = DefaultHandler); 60 | PROVIDE(SPIM3 = DefaultHandler); 61 | -------------------------------------------------------------------------------- /firmware/hal/link-flash.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE RAM is split as follows */ 4 | /* - 8 AHB slaves, each connected to a 2x4 KB RAM sections */ 5 | /* - the 9th AHB slave is connected to 6x32 KB RAM sections */ 6 | /* NOTE all RAM is aliased at address 0x0080_0000 for use as Code RAM */ 7 | /* FLASH : ORIGIN = 0x1000, LENGTH = 0x7F000 */ 8 | FLASH : ORIGIN = 0, LENGTH = 0x7F000 9 | RAM : ORIGIN = 0x20000008, LENGTH = 256K 10 | } 11 | 12 | ENTRY(Reset); 13 | PROVIDE(__stack_top__ = ORIGIN(RAM) + LENGTH(RAM)); 14 | PROVIDE(__ram_start__ = ORIGIN(RAM)); 15 | 16 | SECTIONS 17 | { 18 | .vectors : 19 | { 20 | KEEP(*(.vectors)); 21 | } > FLASH 22 | 23 | .rodata : 24 | { 25 | *(.rodata .rodata.*); 26 | . = ALIGN(4); 27 | } > FLASH 28 | 29 | .text : 30 | { 31 | *(.text .text.*); 32 | } > FLASH 33 | 34 | .init : 35 | { 36 | _sinit = .; 37 | KEEP(*(.init.*)); 38 | /* no ALIGN because this section's size is always multiple of 4 bytes */ 39 | _einit = .; 40 | } > FLASH 41 | 42 | .uninit __ram_start__ (NOLOAD) : 43 | { 44 | *(.uninit.*); 45 | . = ALIGN(4); 46 | } > RAM 47 | 48 | .bss ADDR(.uninit) + SIZEOF(.uninit) (NOLOAD) : 49 | { 50 | _sbss = .; 51 | *(.bss .bss.*); 52 | . = ALIGN(4); 53 | _ebss = .; 54 | } > RAM 55 | 56 | .data ADDR(.bss) + SIZEOF(.bss) : 57 | { 58 | _sdata = .; 59 | *(.data .data.*); 60 | . = ALIGN(4); 61 | _edata = .; 62 | } > RAM AT>FLASH 63 | 64 | _sidata = LOADADDR(.data); 65 | 66 | .binfmt (INFO) : 67 | { 68 | *(.binfmt.*); 69 | } 70 | 71 | /* ## Discarded sections */ 72 | /DISCARD/ : 73 | { 74 | *(.ARM.exidx); 75 | *(.ARM.exidx.*); 76 | *(.ARM.extab.*); 77 | } 78 | } 79 | 80 | ASSERT(SIZEOF(.binfmt) < 16384, "SIZEOF(.binfmt) must not exceed 16383 bytes"); 81 | ASSERT(_sinit % 4 == 0 && _einit % 4 == 0, "`.init` section is not 4-byte aligned"); 82 | ASSERT(ADDR(.vectors) == ORIGIN(FLASH), "vector table has been misplaced"); 83 | 84 | INCLUDE interrupts.x 85 | -------------------------------------------------------------------------------- /firmware/hal/link-ram.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE RAM is split as follows */ 4 | /* - 8 AHB slaves, each connected to a 2x4 KB RAM sections */ 5 | /* - the 9th AHB slave is connected to 6x32 KB RAM sections */ 6 | /* NOTE all RAM is aliased at address 0x0080_0000 for use as Code RAM */ 7 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 8 | } 9 | 10 | ENTRY(Reset); 11 | PROVIDE(__stack_top__ = ORIGIN(RAM) + LENGTH(RAM)); 12 | PROVIDE(__ram_start__ = ORIGIN(RAM)); 13 | 14 | SECTIONS 15 | { 16 | /* stack located here */ 17 | 18 | .uninit __ram_start__ (NOLOAD) : 19 | { 20 | *(.uninit.*); 21 | . = ALIGN(4); 22 | } > RAM 23 | 24 | .bss ADDR(.uninit) + SIZEOF(.uninit) (NOLOAD) : 25 | { 26 | _sbss = .; 27 | *(.bss .bss.*); 28 | . = ALIGN(4); 29 | _ebss = .; 30 | } > RAM 31 | 32 | .data ADDR(.bss) + SIZEOF(.bss) : 33 | { 34 | *(.data .data.*); 35 | . = ALIGN(4); 36 | } > RAM 37 | 38 | .init ADDR(.data) + SIZEOF(.data) : 39 | { 40 | _sinit = .; 41 | KEEP(*(.init.*)); 42 | /* no ALIGN because this section's size is always multiple of 4 bytes */ 43 | _einit = .; 44 | } > RAM 45 | 46 | .rodata ADDR(.init) + SIZEOF(.init) : 47 | { 48 | *(.rodata .rodata.*); 49 | . = ALIGN(4); 50 | } > RAM 51 | 52 | .text ADDR(.rodata) + SIZEOF(.rodata) : 53 | { 54 | *(.text .text.*); 55 | /* `.vectors` alignment requirement given the size of the vector table */ 56 | . = ALIGN(256); 57 | } > RAM 58 | 59 | .vectors ADDR(.text) + SIZEOF(.text) : 60 | { 61 | KEEP(*(.vectors)); 62 | } > RAM 63 | 64 | .binfmt (INFO) : 65 | { 66 | *(.binfmt.*); 67 | } 68 | 69 | /* ## Discarded sections */ 70 | /DISCARD/ : 71 | { 72 | *(.ARM.exidx); 73 | *(.ARM.exidx.*); 74 | *(.ARM.extab.*); 75 | } 76 | } 77 | 78 | ASSERT(SIZEOF(.binfmt) < 16384, "SIZEOF(.binfmt) must not exceed 16383 bytes"); 79 | ASSERT(_sinit % 4 == 0 && _einit % 4 == 0, "`.init` section is not 4-byte aligned"); 80 | 81 | INCLUDE interrupts.x 82 | -------------------------------------------------------------------------------- /firmware/hal/src/atomic.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | marker::PhantomData, 3 | sync::atomic::{AtomicU8, Ordering}, 4 | }; 5 | 6 | macro_rules! derive { 7 | ($e:ident) => { 8 | unsafe impl crate::atomic::Enum for $e { 9 | unsafe fn from_u8(val: u8) -> Self { 10 | core::mem::transmute(val) 11 | } 12 | 13 | fn to_u8(self) -> u8 { 14 | self as u8 15 | } 16 | } 17 | }; 18 | } 19 | 20 | pub struct Atomic { 21 | inner: AtomicU8, 22 | _marker: PhantomData, 23 | } 24 | 25 | /// # Safety 26 | /// - Must be `repr(u8)` C-like enum that covers the range of values `0..=N`, where `N > 0` 27 | pub unsafe trait Enum: Copy { 28 | unsafe fn from_u8(x: u8) -> Self; 29 | fn to_u8(self) -> u8; 30 | } 31 | 32 | impl Atomic { 33 | /// Initializes an atomic enum with a value of `0` 34 | pub const fn new() -> Self { 35 | Self { 36 | inner: AtomicU8::new(0), 37 | _marker: PhantomData, 38 | } 39 | } 40 | 41 | pub fn load(&self) -> E 42 | where 43 | E: Enum, 44 | { 45 | unsafe { E::from_u8(self.inner.load(Ordering::Relaxed)) } 46 | } 47 | 48 | pub fn store(&self, e: E) 49 | where 50 | E: Enum, 51 | { 52 | self.inner.store(e.to_u8(), Ordering::Relaxed) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /firmware/hal/src/clock.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::AtomicBool; 2 | 3 | use pac::CLOCK; 4 | 5 | #[tasks::declare] 6 | mod task { 7 | use core::sync::atomic::Ordering; 8 | 9 | use pac::CLOCK; 10 | 11 | use crate::Interrupt0; 12 | 13 | use super::{Event, STARTED}; 14 | 15 | fn init() { 16 | CLOCK::borrow_unchecked(|clock| { 17 | clock.TASKS_HFCLKSTART.write(|w| w.TASKS_HFCLKSTART(1)); 18 | semidap::info!("started HFXO"); 19 | 20 | unsafe { clock.INTENSET.write(|w| w.HFCLKSTARTED(1)) } 21 | }); 22 | 23 | unsafe { crate::unmask0(&[Interrupt0::POWER_CLOCK]) } 24 | } 25 | 26 | fn CLOCK() -> Option<()> { 27 | semidap::trace!("CLOCK"); 28 | 29 | match Event::next()? { 30 | Event::HFCLKSTARTED => { 31 | semidap::info!("HFXO is stable"); 32 | STARTED.store(true, Ordering::Relaxed); 33 | } 34 | } 35 | 36 | None 37 | } 38 | } 39 | 40 | static STARTED: AtomicBool = AtomicBool::new(false); 41 | 42 | #[cfg(feature = "radio")] 43 | pub async fn has_stabilized() { 44 | use core::{sync::atomic::Ordering, task::Poll}; 45 | 46 | crate::poll_fn(|| { 47 | if STARTED.load(Ordering::Relaxed) { 48 | Poll::Ready(()) 49 | } else { 50 | Poll::Pending 51 | } 52 | }) 53 | .await 54 | } 55 | 56 | #[cfg(feature = "usb")] 57 | pub fn is_stable() -> bool { 58 | use core::sync::atomic::Ordering; 59 | 60 | STARTED.load(Ordering::Relaxed) 61 | } 62 | 63 | enum Event { 64 | HFCLKSTARTED, 65 | } 66 | 67 | impl Event { 68 | fn next() -> Option { 69 | CLOCK::borrow_unchecked(|clock| { 70 | if clock.EVENTS_HFCLKSTARTED.read().bits() != 0 { 71 | clock.EVENTS_HFCLKSTARTED.zero(); 72 | return Some(Event::HFCLKSTARTED); 73 | } 74 | 75 | None 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /firmware/hal/src/errata.rs: -------------------------------------------------------------------------------- 1 | // Reference: nRF52840_Rev_1_Errata_v1.4.pdf 2 | 3 | #![allow(dead_code)] 4 | 5 | /// USBD might not power up 6 | pub unsafe fn e171a() { 7 | if (0x4006_EC00 as *const u32).read_volatile() == 0 { 8 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 9 | } 10 | (0x4006_EC14 as *mut u32).write_volatile(0xC0); 11 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 12 | } 13 | 14 | /// USBD might not power up 15 | pub unsafe fn e171b() { 16 | if (0x4006_EC00 as *const u32).read_volatile() == 0 { 17 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 18 | } 19 | (0x4006_EC14 as *mut u32).write_volatile(0); 20 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 21 | } 22 | 23 | /// USBD cannot be enabled 24 | pub unsafe fn e187a() { 25 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 26 | (0x4006_ED14 as *mut u32).write_volatile(3); 27 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 28 | } 29 | 30 | /// USBD cannot be enabled 31 | pub unsafe fn e187b() { 32 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 33 | (0x4006_ED14 as *mut u32).write_volatile(0); 34 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 35 | } 36 | -------------------------------------------------------------------------------- /firmware/hal/src/led.rs: -------------------------------------------------------------------------------- 1 | //! LEDs 2 | 3 | use pac::p0; 4 | 5 | // // MDK 6 | // pub(crate) const RED: u32 = 1 << 23; 7 | // pub(crate) const GREEN: u32 = 1 << 22; 8 | // pub(crate) const BLUE: u32 = 1 << 24; 9 | 10 | // Dongle 11 | // pub(crate) const RED: u32 = 1 << 8; 12 | // pub(crate) const GREEN: u32 = 1 << 6; 13 | // pub(crate) const BLUE: u32 = 1 << 12; 14 | 15 | // DK 16 | pub(crate) const RED: u32 = 1 << 13; 17 | pub(crate) const GREEN: u32 = 1 << 14; 18 | pub(crate) const BLUE: u32 = 1 << 15; 19 | 20 | /// Red LED 21 | pub struct Red; 22 | 23 | impl Red { 24 | /// Turns the LED off 25 | pub fn off(&self) { 26 | unsafe { p0::OUTSET::address().write_volatile(RED) } 27 | } 28 | 29 | /// Turns the LED on 30 | pub fn on(&self) { 31 | unsafe { p0::OUTCLR::address().write_volatile(RED) } 32 | } 33 | } 34 | 35 | /// Green LED 36 | pub struct Green; 37 | 38 | impl Green { 39 | /// Turns the LED off 40 | pub fn off(&self) { 41 | unsafe { p0::OUTSET::address().write_volatile(GREEN) } 42 | } 43 | 44 | /// Turns the LED on 45 | pub fn on(&self) { 46 | unsafe { p0::OUTCLR::address().write_volatile(GREEN) } 47 | } 48 | } 49 | 50 | /// Blue LED 51 | pub struct Blue; 52 | 53 | impl Blue { 54 | /// Turns the LED off 55 | pub fn off(&self) { 56 | unsafe { p0::OUTSET::address().write_volatile(BLUE) } 57 | } 58 | 59 | /// Turns the LED on 60 | pub fn on(&self) { 61 | unsafe { p0::OUTCLR::address().write_volatile(BLUE) } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /firmware/hal/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Hardware Abstraction Layer 2 | 3 | #![deny(missing_docs)] 4 | #![deny(warnings)] 5 | #![no_std] 6 | 7 | use core::{ 8 | future::Future, 9 | marker::{PhantomData, Unpin}, 10 | pin::Pin, 11 | sync::{self, atomic::Ordering}, 12 | task::{Context, Poll}, 13 | }; 14 | 15 | use cm::{DWT, NVIC}; 16 | use pac::FICR; 17 | 18 | #[cfg(any(feature = "radio", feature = "usb"))] 19 | #[macro_use] 20 | mod atomic; 21 | 22 | #[cfg(any(feature = "radio", feature = "usb"))] 23 | mod clock; 24 | mod errata; 25 | pub mod led; 26 | mod mem; 27 | pub mod p0; 28 | #[cfg(feature = "radio")] 29 | pub mod radio; 30 | mod reset; 31 | pub mod spi; 32 | pub mod time; 33 | pub mod timer; 34 | #[cfg(feature = "usb")] 35 | pub mod usbd; 36 | mod util; 37 | 38 | /// Reads the 32-bit cycle counter 39 | pub fn cyccnt() -> u32 { 40 | // NOTE(borrow_unchecked) single-instruction read with no side effects 41 | DWT::borrow_unchecked(|dwt| dwt.CYCCNT.read()) 42 | } 43 | 44 | /// Returns the device identifier 45 | pub fn deviceid() -> u64 { 46 | u64::from(deviceid0()) | u64::from(deviceid1()) << 32 47 | } 48 | 49 | /// Returns the least-significant bits of the device identifier 50 | pub fn deviceid0() -> u32 { 51 | // NOTE(borrow_unchecked) read-only registers 52 | FICR::borrow_unchecked(|ficr| ficr.DEVICEID0.read().bits()) 53 | } 54 | 55 | /// Returns the most-significant bits of the device identifier 56 | pub fn deviceid1() -> u32 { 57 | // NOTE(borrow_unchecked) read-only registers 58 | FICR::borrow_unchecked(|ficr| ficr.DEVICEID1.read().bits()) 59 | } 60 | 61 | struct NotSync { 62 | inner: PhantomData<*mut ()>, 63 | } 64 | 65 | impl NotSync { 66 | fn new() -> Self { 67 | NotSync { inner: PhantomData } 68 | } 69 | } 70 | 71 | unsafe impl Send for NotSync {} 72 | 73 | #[allow(dead_code)] 74 | struct NotSendOrSync { 75 | inner: PhantomData<*mut ()>, 76 | } 77 | 78 | #[allow(dead_code)] 79 | impl NotSendOrSync { 80 | fn new() -> Self { 81 | Self { inner: PhantomData } 82 | } 83 | } 84 | 85 | // NOTE must be followed by a volatile STORE operation 86 | fn dma_start() { 87 | sync::atomic::compiler_fence(Ordering::Release) 88 | } 89 | 90 | // NOTE must be preced by a volatile LOAD operation 91 | fn dma_end() { 92 | sync::atomic::compiler_fence(Ordering::Acquire) 93 | } 94 | 95 | #[allow(dead_code)] 96 | async fn poll_fn(f: F) -> T 97 | where 98 | F: FnMut() -> Poll + Unpin, 99 | { 100 | struct PollFn { 101 | f: F, 102 | } 103 | 104 | impl Future for PollFn 105 | where 106 | F: FnMut() -> Poll + Unpin, 107 | { 108 | type Output = T; 109 | 110 | fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { 111 | (self.get_mut().f)() 112 | } 113 | } 114 | 115 | PollFn { f }.await 116 | } 117 | 118 | /// # Safety 119 | /// Must not be nested 120 | #[allow(dead_code)] 121 | unsafe fn atomic0(interrupt: Interrupt0, f: impl FnOnce() -> T) -> T { 122 | mask0(&[interrupt]); 123 | sync::atomic::compiler_fence(Ordering::SeqCst); 124 | let r = f(); 125 | sync::atomic::compiler_fence(Ordering::SeqCst); 126 | unmask0(&[interrupt]); 127 | r 128 | } 129 | 130 | /// # Safety 131 | /// Must not be nested 132 | #[allow(dead_code)] 133 | unsafe fn atomic1(interrupt: Interrupt1, f: impl FnOnce() -> T) -> T { 134 | mask1(&[interrupt]); 135 | sync::atomic::compiler_fence(Ordering::SeqCst); 136 | let r = f(); 137 | sync::atomic::compiler_fence(Ordering::SeqCst); 138 | unmask1(&[interrupt]); 139 | r 140 | } 141 | 142 | #[allow(dead_code)] 143 | fn pend1(interrupt: Interrupt1) { 144 | NVIC::borrow_unchecked(|nvic| nvic.ISPR1.write(1 << (interrupt as u8 - 32))); 145 | } 146 | 147 | #[allow(dead_code)] 148 | fn mask0(interrupts: &[Interrupt0]) { 149 | let mut val = 0; 150 | for interrupt in interrupts.iter().cloned() { 151 | val |= 1 << interrupt as u8; 152 | } 153 | 154 | if val != 0 { 155 | // NOTE(borrow_unchecked) single-instruction write 156 | NVIC::borrow_unchecked(|nvic| nvic.ICER0.write(val)); 157 | } 158 | } 159 | 160 | #[allow(dead_code)] 161 | fn mask1(interrupts: &[Interrupt1]) { 162 | let mut val = 0; 163 | for interrupt in interrupts.iter().cloned() { 164 | val |= 1 << (interrupt as u8 - 32); 165 | } 166 | 167 | if val != 0 { 168 | // NOTE(borrow_unchecked) single-instruction write 169 | NVIC::borrow_unchecked(|nvic| nvic.ICER1.write(val)); 170 | } 171 | } 172 | 173 | #[allow(dead_code)] 174 | unsafe fn unmask0(interrupts: &[Interrupt0]) { 175 | let mut val = 0; 176 | for interrupt in interrupts.iter().cloned() { 177 | val |= 1 << interrupt as u8; 178 | } 179 | 180 | if val != 0 { 181 | // NOTE(borrow_unchecked) single-instruction write 182 | NVIC::borrow_unchecked(|nvic| nvic.ISER0.write(val)); 183 | } 184 | } 185 | 186 | unsafe fn unmask1(interrupts: &[Interrupt1]) { 187 | let mut val = 0; 188 | for interrupt in interrupts.iter().cloned() { 189 | val |= 1 << (interrupt as u8 - 32); 190 | } 191 | 192 | if val != 0 { 193 | // NOTE(borrow_unchecked) single-instruction write 194 | NVIC::borrow_unchecked(|nvic| nvic.ISER1.write(val)); 195 | } 196 | } 197 | 198 | /// Interrupts 0..32 199 | #[allow(missing_docs)] 200 | #[allow(non_camel_case_types)] 201 | #[derive(Clone, Copy)] 202 | pub enum Interrupt0 { 203 | POWER_CLOCK = 0, 204 | RADIO = 1, 205 | UARTE0_UART0 = 2, 206 | SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 = 3, 207 | SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 = 4, 208 | NFCT = 5, 209 | GPIOTE = 6, 210 | SAADC = 7, 211 | TIMER0 = 8, 212 | TIMER1 = 9, 213 | TIMER2 = 10, 214 | RTC0 = 11, 215 | TEMP = 12, 216 | RNG = 13, 217 | ECB = 14, 218 | CCM_AAR = 15, 219 | WDT = 16, 220 | RTC1 = 17, 221 | QDEC = 18, 222 | COMP_LPCOMP = 19, 223 | SWI0_EGU0 = 20, 224 | SWI1_EGU1 = 21, 225 | SWI2_EGU2 = 22, 226 | SWI3_EGU3 = 23, 227 | SWI4_EGU4 = 24, 228 | SWI5_EGU5 = 25, 229 | TIMER3 = 26, 230 | TIMER4 = 27, 231 | PWM0 = 28, 232 | PDM = 29, 233 | } 234 | 235 | /// Interrupts 32.. 236 | #[allow(missing_docs)] 237 | #[allow(non_camel_case_types)] 238 | #[derive(Clone, Copy)] 239 | pub enum Interrupt1 { 240 | MWU = 32, 241 | PWM1 = 33, 242 | PWM2 = 34, 243 | SPIM2_SPIS2_SPI2 = 35, 244 | RTC2 = 36, 245 | I2S = 37, 246 | FPU = 38, 247 | USBD = 39, 248 | UARTE1 = 40, 249 | QSPI = 41, 250 | CRYPTOCELL = 42, 251 | PWM3 = 45, 252 | SPIM3 = 47, 253 | } 254 | 255 | // split this interrupt -- it makes my life much easier 256 | #[cfg(any(feature = "radio", feature = "usb"))] 257 | #[no_mangle] 258 | unsafe extern "C" fn POWER_CLOCK() { 259 | extern "C" { 260 | fn CLOCK(); 261 | #[cfg(feature = "usb")] 262 | fn POWER(); 263 | } 264 | 265 | CLOCK(); 266 | #[cfg(feature = "usb")] 267 | POWER(); 268 | } 269 | -------------------------------------------------------------------------------- /firmware/hal/src/mem.rs: -------------------------------------------------------------------------------- 1 | use pool::pool; 2 | 3 | // for radio packets we'll use these blocks as: 4 | // { padding: 3B, len: 1B, data: 127B, LQI: 1B } 5 | // 6 | // and for USB packets we'll use them: 7 | // { padding: 4B, data: 64B, padding: 63B } 8 | // 9 | // this let's us convert between them with zero copies 10 | // 11 | // the padding is needed because USB.data must be 4-byte aligned 12 | pool!(pub P: [u8; 132]); 13 | -------------------------------------------------------------------------------- /firmware/hal/src/p0.rs: -------------------------------------------------------------------------------- 1 | //! Port 0 2 | 3 | use core::sync::atomic::{AtomicBool, Ordering}; 4 | 5 | /// Port 0 6 | pub struct P0 { 7 | // pub pin0: Pin, // not routed (?) 8 | // pub pin1: Pin, // not routed (?) 9 | /// P0.2 10 | pub pin2: Pin, 11 | /// P0.3 12 | pub pin3: Pin, 13 | /// P0.4 14 | pub pin4: Pin, 15 | /// P0.5 16 | pub pin5: Pin, 17 | /// P0.6 18 | pub pin6: Pin, 19 | /// P0.7 20 | pub pin7: Pin, 21 | /// P0.8 22 | pub pin8: Pin, 23 | // pub pin9: Pin, // NFC1 24 | // pub pin10: Pin, // NRF2 25 | /// P0.11 26 | pub pin11: Pin, 27 | /// P0.12 28 | pub pin12: Pin, 29 | /// P0.13 30 | pub pin13: Pin, 31 | /// P0.14 32 | pub pin14: Pin, 33 | /// P0.15 34 | pub pin15: Pin, 35 | /// P0.16 36 | pub pin16: Pin, 37 | /// P0.17 38 | pub pin17: Pin, 39 | // pub pin18: Pin, // not routed (?) 40 | // pub pin19: Pin, // RXD 41 | // pub pin20: Pin, // TXD 42 | /// P0.21 43 | pub pin21: Pin, 44 | // pub pin22: Pin, // green LED 45 | // pub pin23: Pin, // red LED 46 | // pub pin24: Pin, // blue LED 47 | /// P0.25 48 | pub pin25: Pin, 49 | /// P0.26 50 | pub pin26: Pin, 51 | /// P0.27 52 | pub pin27: Pin, 53 | /// P0.28 54 | pub pin28: Pin, 55 | /// P0.29 56 | pub pin29: Pin, 57 | /// P0.30 58 | pub pin30: Pin, 59 | /// P0.31 60 | pub pin31: Pin, 61 | } 62 | 63 | /// Output level 64 | #[derive(Clone, Copy, PartialEq)] 65 | pub enum Level { 66 | /// Low level (0) 67 | Low, 68 | /// High level (1) 69 | High, 70 | } 71 | 72 | /// Output pin 73 | pub struct Output(pub(crate) u8); 74 | 75 | impl Output { 76 | /// Changes the output `level` of the pin 77 | pub fn set(&mut self, level: Level) { 78 | let mask = 1 << self.0; 79 | 80 | unsafe { 81 | match level { 82 | Level::Low => pac::p0::OUTCLR::address().write_volatile(mask), 83 | Level::High => pac::p0::OUTSET::address().write_volatile(mask), 84 | } 85 | } 86 | } 87 | 88 | /// Sets the pin high 89 | pub fn set_high(&mut self) { 90 | self.set(Level::High) 91 | } 92 | 93 | /// Sets the pin low 94 | pub fn set_low(&mut self) { 95 | self.set(Level::Low) 96 | } 97 | } 98 | 99 | /// P0 pin 100 | pub struct Pin(pub(crate) u8); 101 | 102 | impl Pin { 103 | /// Configures the pin as an input pin 104 | #[cfg(TODO)] 105 | pub fn into_input(self, pullup: Level) -> Input { 106 | unsafe { 107 | let (pu, sense) = match pullup { 108 | Level::Low => (1, 2), 109 | Level::High => (3, 3), 110 | }; 111 | 112 | let mut w = pac::p0::pin_cnf0::W::zero(); 113 | w.INPUT(1).PULL(pu).SENSE(sense); 114 | pac::p0::PIN_CNF0::address() 115 | .offset(self.0.into()) 116 | .write_volatile(w.into()); 117 | } 118 | 119 | Input(self.0) 120 | } 121 | 122 | /// Configures the pin as an output pin 123 | pub fn into_output(self, level: Level) -> Output { 124 | unsafe { 125 | if level == Level::High { 126 | pac::p0::OUTSET::address().write_volatile(1 << self.0); 127 | pac::p0::DIRSET::address().write_volatile(1 << self.0); 128 | } 129 | 130 | Output(self.0) 131 | } 132 | } 133 | } 134 | 135 | static TAKEN: AtomicBool = AtomicBool::new(false); 136 | 137 | /// Returns all P0 pins 138 | pub fn claim() -> P0 { 139 | if TAKEN 140 | .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) 141 | .is_ok() 142 | { 143 | P0 { 144 | pin2: Pin(2), 145 | pin3: Pin(3), 146 | pin4: Pin(4), 147 | pin5: Pin(5), 148 | pin6: Pin(6), 149 | pin7: Pin(7), 150 | pin8: Pin(8), 151 | pin11: Pin(11), 152 | pin12: Pin(12), 153 | pin13: Pin(13), 154 | pin14: Pin(14), 155 | pin15: Pin(15), 156 | pin16: Pin(16), 157 | pin17: Pin(17), 158 | pin21: Pin(21), 159 | pin25: Pin(25), 160 | pin26: Pin(26), 161 | pin27: Pin(27), 162 | pin28: Pin(28), 163 | pin29: Pin(29), 164 | pin30: Pin(30), 165 | pin31: Pin(31), 166 | } 167 | } else { 168 | semidap::panic!("port `p0` has already been claimed") 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /firmware/hal/src/reset.rs: -------------------------------------------------------------------------------- 1 | use core::{mem, ptr}; 2 | 3 | use cm::{DCB, DWT, NVIC}; 4 | use pac::{p0, CLOCK, P0, RTC0}; 5 | 6 | use crate::led; 7 | 8 | #[no_mangle] 9 | unsafe extern "C" fn Reset() { 10 | // NOTE(borrow_unchecked) interrupts disabled; this runs before user code 11 | // enable the cycle counter and start it with an initial count of 0 12 | DCB::borrow_unchecked(|dcb| dcb.DEMCR.rmw(|_, w| w.TRCENA(1))); 13 | DWT::borrow_unchecked(|dwt| { 14 | dwt.CYCCNT.write(0); 15 | dwt.CTRL.rmw(|_, w| w.CYCCNTENA(1)); 16 | }); 17 | 18 | CLOCK::borrow_unchecked(|clock| { 19 | // use the external crystal (LFXO) as the low-frequency clock (LFCLK) source 20 | clock.LFCLKSRC.write(|w| w.SRC(1)); 21 | 22 | // start the LFXO 23 | clock.TASKS_LFCLKSTART.write(|w| w.TASKS_LFCLKSTART(1)); 24 | }); 25 | 26 | // start the RTC with a counter of 0 27 | RTC0::borrow_unchecked(|rtc| { 28 | rtc.TASKS_CLEAR.write(|w| w.TASKS_CLEAR(1)); 29 | rtc.TASKS_START.write(|w| w.TASKS_START(1)); 30 | }); 31 | 32 | // zero .bss 33 | extern "C" { 34 | static mut _sbss: u32; 35 | static mut _ebss: u32; 36 | } 37 | 38 | let sbss = &mut _sbss as *mut u32; 39 | let ebss = &mut _ebss as *mut u32; 40 | ptr::write_bytes( 41 | sbss, 42 | 0, 43 | (ebss as usize - sbss as usize) / mem::size_of::(), 44 | ); 45 | 46 | // init .data 47 | #[cfg(feature = "flash")] 48 | { 49 | extern "C" { 50 | static mut _sdata: u32; 51 | static mut _edata: u32; 52 | static mut _sidata: u32; 53 | } 54 | 55 | let sdata = &mut _sdata as *mut u32; 56 | let edata = &mut _edata as *mut u32; 57 | let sidata = &_sidata as *const u32; 58 | ptr::copy_nonoverlapping( 59 | sidata, 60 | sdata, 61 | (edata as usize - sdata as usize) / mem::size_of::(), 62 | ); 63 | } 64 | 65 | // NOTE this is a memory barrier -- .bss will be zeroed before the code that comes after this 66 | asm::disable_irq(); 67 | 68 | // seal some peripherals so they cannot be used from the application 69 | CLOCK::seal(); 70 | DCB::seal(); 71 | DWT::seal(); 72 | NVIC::seal(); 73 | P0::seal(); 74 | RTC0::seal(); 75 | 76 | // configure I/O pins 77 | // set outputs high (LEDs off) 78 | p0::OUTSET::address().write_volatile(led::RED | led::BLUE | led::GREEN); 79 | // set pins as output 80 | p0::DIRSET::address().write_volatile(led::RED | led::BLUE | led::GREEN); 81 | 82 | // run initializers 83 | extern "C" { 84 | static _sinit: usize; 85 | static _einit: usize; 86 | } 87 | 88 | let mut sinit = &_sinit as *const usize; 89 | let einit = &_einit as *const usize; 90 | while sinit < einit { 91 | let f: unsafe extern "C" fn() = mem::transmute(sinit.read()); 92 | f(); 93 | sinit = sinit.add(1); 94 | } 95 | 96 | extern "Rust" { 97 | fn main() -> !; 98 | } 99 | 100 | asm::enable_irq(); 101 | 102 | main() 103 | } 104 | 105 | #[no_mangle] 106 | fn __semidap_timestamp() -> u32 { 107 | crate::cyccnt() >> 6 108 | } 109 | 110 | #[repr(C)] 111 | union Vector { 112 | stack_pointer: *const u32, 113 | handler: unsafe extern "C" fn(), 114 | reserved: usize, 115 | } 116 | 117 | extern "C" { 118 | static __stack_top__: u32; 119 | 120 | // Cortex-M exceptions 121 | fn NMI(); 122 | fn HardFault(); 123 | fn MemManage(); 124 | fn BusFault(); 125 | fn UsageFault(); 126 | fn SVCall(); 127 | fn DebugMonitor(); 128 | fn PendSV(); 129 | fn SysTick(); 130 | 131 | // nRF52840 interrupts 132 | fn POWER_CLOCK(); 133 | fn RADIO(); 134 | fn UARTE0_UART0(); 135 | fn SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0(); 136 | fn SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1(); 137 | fn NFCT(); 138 | fn GPIOTE(); 139 | fn SAADC(); 140 | fn TIMER0(); 141 | fn TIMER1(); 142 | fn TIMER2(); 143 | fn RTC0(); 144 | fn TEMP(); 145 | fn RNG(); 146 | fn ECB(); 147 | fn CCM_AAR(); 148 | fn WDT(); 149 | fn RTC1(); 150 | fn QDEC(); 151 | fn COMP_LPCOMP(); 152 | fn SWI0_EGU0(); 153 | fn SWI1_EGU1(); 154 | fn SWI2_EGU2(); 155 | fn SWI3_EGU3(); 156 | fn SWI4_EGU4(); 157 | fn SWI5_EGU5(); 158 | fn TIMER3(); 159 | fn TIMER4(); 160 | fn PWM0(); 161 | fn PDM(); 162 | fn MWU(); 163 | fn PWM1(); 164 | fn PWM2(); 165 | fn SPIM2_SPIS2_SPI2(); 166 | fn RTC2(); 167 | fn I2S(); 168 | fn FPU(); 169 | fn USBD(); 170 | fn UARTE1(); 171 | fn QSPI(); 172 | fn CRYPTOCELL(); 173 | fn PWM3(); 174 | fn SPIM3(); 175 | } 176 | 177 | #[link_section = ".vectors"] 178 | #[no_mangle] 179 | static mut VECTORS: [Vector; 64] = [ 180 | // Cortex-M exceptions 181 | Vector { 182 | stack_pointer: unsafe { &__stack_top__ as *const u32 }, 183 | }, 184 | Vector { handler: Reset }, 185 | Vector { handler: NMI }, 186 | Vector { handler: HardFault }, 187 | Vector { handler: MemManage }, 188 | Vector { handler: BusFault }, 189 | Vector { 190 | handler: UsageFault, 191 | }, 192 | Vector { reserved: 0 }, 193 | Vector { reserved: 0 }, 194 | Vector { reserved: 0 }, 195 | Vector { reserved: 0 }, 196 | Vector { handler: SVCall }, 197 | Vector { 198 | handler: DebugMonitor, 199 | }, 200 | Vector { reserved: 0 }, 201 | Vector { handler: PendSV }, 202 | Vector { handler: SysTick }, 203 | // nRF52840 interrupts 204 | Vector { 205 | handler: POWER_CLOCK, // 0 206 | }, 207 | Vector { 208 | handler: RADIO, // 1 209 | }, 210 | Vector { 211 | handler: UARTE0_UART0, // 2 212 | }, 213 | Vector { 214 | handler: SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, // 3 215 | }, 216 | Vector { 217 | handler: SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, // 4 218 | }, 219 | Vector { 220 | handler: NFCT, // 5 221 | }, 222 | Vector { 223 | handler: GPIOTE, // 6 224 | }, 225 | Vector { 226 | handler: SAADC, // 7 227 | }, 228 | Vector { 229 | handler: TIMER0, // 8 230 | }, 231 | Vector { 232 | handler: TIMER1, // 9 233 | }, 234 | Vector { 235 | handler: TIMER2, // 10 236 | }, 237 | Vector { 238 | handler: RTC0, // 11 239 | }, 240 | Vector { 241 | handler: TEMP, // 12 242 | }, 243 | Vector { 244 | handler: RNG, // 13 245 | }, 246 | Vector { 247 | handler: ECB, // 14 248 | }, 249 | Vector { 250 | handler: CCM_AAR, // 15 251 | }, 252 | Vector { 253 | handler: WDT, // 16 254 | }, 255 | Vector { 256 | handler: RTC1, // 17 257 | }, 258 | Vector { 259 | handler: QDEC, // 18 260 | }, 261 | Vector { 262 | handler: COMP_LPCOMP, // 19 263 | }, 264 | Vector { 265 | handler: SWI0_EGU0, // 20 266 | }, 267 | Vector { 268 | handler: SWI1_EGU1, // 21 269 | }, 270 | Vector { 271 | handler: SWI2_EGU2, // 22 272 | }, 273 | Vector { 274 | handler: SWI3_EGU3, // 23 275 | }, 276 | Vector { 277 | handler: SWI4_EGU4, // 24 278 | }, 279 | Vector { 280 | handler: SWI5_EGU5, // 25 281 | }, 282 | Vector { 283 | handler: TIMER3, // 26 284 | }, 285 | Vector { 286 | handler: TIMER4, // 27 287 | }, 288 | Vector { 289 | handler: PWM0, // 28 290 | }, 291 | Vector { 292 | handler: PDM, // 29 293 | }, 294 | Vector { reserved: 0 }, // 30 295 | Vector { reserved: 0 }, // 31 296 | Vector { 297 | handler: MWU, // 32 298 | }, 299 | Vector { 300 | handler: PWM1, // 33 301 | }, 302 | Vector { 303 | handler: PWM2, // 34 304 | }, 305 | Vector { 306 | handler: SPIM2_SPIS2_SPI2, // 35 307 | }, 308 | Vector { 309 | handler: RTC2, // 36 310 | }, 311 | Vector { 312 | handler: I2S, // 37 313 | }, 314 | Vector { 315 | handler: FPU, // 38 316 | }, 317 | Vector { 318 | handler: USBD, // 39 319 | }, 320 | Vector { 321 | handler: UARTE1, // 40 322 | }, 323 | Vector { 324 | handler: QSPI, // 41 325 | }, 326 | Vector { 327 | handler: CRYPTOCELL, // 42 328 | }, 329 | Vector { reserved: 0 }, // 43 330 | Vector { reserved: 0 }, // 44 331 | Vector { 332 | handler: PWM3, // 45 333 | }, 334 | Vector { reserved: 0 }, // 46 335 | Vector { 336 | handler: SPIM3, // 47 337 | }, 338 | ]; 339 | -------------------------------------------------------------------------------- /firmware/hal/src/spi.rs: -------------------------------------------------------------------------------- 1 | //! Serial Peripheral Interface 2 | 3 | use core::{ 4 | num::NonZeroU16, 5 | sync::atomic::{AtomicBool, Ordering}, 6 | task::Poll, 7 | }; 8 | 9 | use pac::{p0, SPIM0}; 10 | 11 | use crate::{p0::Pin, Interrupt0, NotSendOrSync}; 12 | 13 | // TODO hand out up to 3 SPIs 14 | static TAKEN: AtomicBool = AtomicBool::new(false); 15 | 16 | #[allow(non_snake_case)] 17 | #[no_mangle] 18 | fn SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0() { 19 | semidap::trace!("SPIM0"); 20 | 21 | DONE.store(true, Ordering::Relaxed); 22 | SPIM0::borrow_unchecked(|spim| spim.EVENTS_END.zero()); 23 | } 24 | 25 | static DONE: AtomicBool = AtomicBool::new(false); 26 | 27 | /// Host-mode SPI 28 | pub struct Spi { 29 | _not_send_or_sync: NotSendOrSync, 30 | } 31 | 32 | impl Spi { 33 | /// Turns the given pins into a host-mode SPI 34 | /// 35 | /// Frequency will be set to 1 Mbps. SPI will operate in "mode 0" 36 | pub fn new(sck: Pin, mosi: Pin, miso: Pin) -> Self { 37 | if TAKEN 38 | .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) 39 | .is_ok() 40 | { 41 | // pin configuration 42 | let out_mask = (1 << sck.0) | (1 << mosi.0); 43 | unsafe { p0::DIRSET::address().write_volatile(out_mask) } 44 | // SCK & MOSI must be set low (this is default after reset) 45 | // MISO must be configured as an input (this is the default after reset) 46 | 47 | SPIM0::borrow_unchecked(|spim| { 48 | unsafe { 49 | spim.INTENSET.write(|w| w.END(1)); 50 | crate::unmask0(&[Interrupt0::SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0]); 51 | } 52 | 53 | // MSB first & mode 0 is the default after reset 54 | spim.PSEL_MISO.write(|w| w.CONNECT(0).PORT(0).PIN(miso.0)); 55 | spim.PSEL_MOSI.write(|w| w.CONNECT(0).PORT(0).PIN(mosi.0)); 56 | spim.PSEL_SCK.write(|w| w.CONNECT(0).PORT(0).PIN(sck.0)); 57 | 58 | // 1 Mbps 59 | const M1: u32 = 0x1000_0000; 60 | spim.FREQUENCY.write(|w| w.FREQUENCY(M1)); 61 | spim.ENABLE.write(|w| w.ENABLE(7)); 62 | }); 63 | 64 | Spi { 65 | _not_send_or_sync: NotSendOrSync::new(), 66 | } 67 | } else { 68 | semidap::panic!("`spi` interface has already been claimed"); 69 | } 70 | } 71 | 72 | /// Reads data from the device by sending it junk data 73 | pub async fn read(&mut self, buf: &mut [u8]) { 74 | if let Some(len) = NonZeroU16::new(buf.len() as u16) { 75 | self.transfer(Transfer::Rx { 76 | ptr: buf.as_mut_ptr() as u32, 77 | len, 78 | }) 79 | .await 80 | } 81 | } 82 | 83 | /// Sends data to the device ignoring the data it sends to us 84 | pub async fn write(&mut self, buf: &[u8]) { 85 | if let Some(len) = NonZeroU16::new(buf.len() as u16) { 86 | self.transfer(Transfer::Tx { 87 | ptr: buf.as_ptr() as u32, 88 | len, 89 | }) 90 | .await 91 | } 92 | } 93 | 94 | async fn transfer(&mut self, transfer: Transfer) { 95 | SPIM0::borrow_unchecked(|spim| { 96 | match transfer { 97 | Transfer::Rx { ptr, len } => { 98 | spim.RXD_MAXCNT.write(|w| w.MAXCNT(len.get())); 99 | spim.RXD_PTR.write(|w| w.PTR(ptr)); 100 | spim.TXD_MAXCNT.write(|w| w.MAXCNT(0)); 101 | } 102 | 103 | Transfer::Tx { ptr, len } => { 104 | spim.TXD_MAXCNT.write(|w| w.MAXCNT(len.get())); 105 | spim.TXD_PTR.write(|w| w.PTR(ptr)); 106 | spim.RXD_MAXCNT.write(|w| w.MAXCNT(0)); 107 | } 108 | 109 | Transfer::RxTx { 110 | rx_ptr, 111 | tx_ptr, 112 | rx_len, 113 | tx_len, 114 | } => { 115 | spim.RXD_MAXCNT.write(|w| w.MAXCNT(rx_len.get())); 116 | spim.RXD_PTR.write(|w| w.PTR(rx_ptr)); 117 | spim.TXD_MAXCNT.write(|w| w.MAXCNT(tx_len.get())); 118 | spim.TXD_PTR.write(|w| w.PTR(tx_ptr)); 119 | } 120 | } 121 | 122 | DONE.store(false, Ordering::Relaxed); 123 | 124 | crate::dma_start(); 125 | spim.TASKS_START.write(|w| w.TASKS_START(1)); 126 | }); 127 | 128 | crate::poll_fn(|| { 129 | if DONE.load(Ordering::Relaxed) { 130 | crate::dma_end(); 131 | Poll::Ready(()) 132 | } else { 133 | Poll::Pending 134 | } 135 | }) 136 | .await; 137 | } 138 | } 139 | 140 | enum Transfer { 141 | Tx { 142 | ptr: u32, 143 | len: NonZeroU16, 144 | }, 145 | 146 | Rx { 147 | ptr: u32, 148 | len: NonZeroU16, 149 | }, 150 | 151 | #[allow(dead_code)] 152 | RxTx { 153 | rx_len: NonZeroU16, 154 | rx_ptr: u32, 155 | tx_len: NonZeroU16, 156 | tx_ptr: u32, 157 | }, 158 | } 159 | 160 | /// Chip Select pin 161 | pub struct ChipSelect(u8); 162 | 163 | impl ChipSelect { 164 | /// Configures the given `pin` as a chip-select pin 165 | pub fn new(pin: Pin) -> Self { 166 | // configure as output and set high 167 | unsafe { 168 | p0::OUTSET::address().write_volatile(1 << pin.0); 169 | p0::DIRSET::address().write_volatile(1 << pin.0); 170 | } 171 | 172 | ChipSelect(pin.0) 173 | } 174 | 175 | /// Selects the SPI device 176 | pub fn select(&mut self) { 177 | unsafe { p0::OUTCLR::address().write_volatile(1 << self.0) } 178 | } 179 | 180 | /// Deselects the SPI device 181 | pub fn deselect(&mut self) { 182 | unsafe { p0::OUTSET::address().write_volatile(1 << self.0) } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /firmware/hal/src/time.rs: -------------------------------------------------------------------------------- 1 | //! Temporal quantification 2 | 3 | use core::{ops, time::Duration}; 4 | 5 | use pac::RTC0; 6 | 7 | pub(crate) fn now() -> u32 { 8 | RTC0::borrow_unchecked(|rtc| rtc.COUNTER.read().into()) 9 | } 10 | 11 | /// A measurement of a monotonically nondecreasing clock. Opaque and only useful 12 | /// with `core::time::Duration` 13 | pub struct Instant { 14 | // TODO turn into `u64` 15 | inner: u32, 16 | } 17 | 18 | impl Instant { 19 | /// Returns an `Instant` corresponding to "now" 20 | pub fn now() -> Self { 21 | Instant { inner: now() } 22 | } 23 | } 24 | 25 | impl ops::Sub for Instant { 26 | type Output = Duration; 27 | 28 | fn sub(self, rhs: Self) -> Duration { 29 | semidap::assert!( 30 | self.inner >= rhs.inner, 31 | "supplied instant is later than self" 32 | ); 33 | 34 | // `ticks` is always less than `1 << 24` 35 | let ticks = self.inner.wrapping_sub(rhs.inner); 36 | #[allow(clippy::suspicious_arithmetic_impl)] 37 | let secs = ticks >> 15; 38 | // one tick is equal to `1e9 / 32768` nanos 39 | // the fraction can be reduced to `1953125 / 64` 40 | // which can be further decomposed as `78125 * (5 / 4) * (5 / 4) * (1 / 41 | // 4)`. Doing the operation this way we can stick to 32-bit arithmetic 42 | // without overflowing the value at any stage 43 | let nanos = 44 | (((ticks % 32768).wrapping_mul(78125) >> 2).wrapping_mul(5) >> 2).wrapping_mul(5) >> 2; 45 | Duration::new(secs.into(), nanos) 46 | } 47 | } 48 | 49 | /// Returns the time elapsed since the last reset (either POR or soft reset) 50 | pub fn uptime() -> Duration { 51 | Instant::now() - Instant { inner: 0 } 52 | } 53 | -------------------------------------------------------------------------------- /firmware/hal/src/timer.rs: -------------------------------------------------------------------------------- 1 | //! Timers 2 | 3 | use core::{ 4 | cmp, 5 | future::Future, 6 | pin::Pin, 7 | sync::atomic::{AtomicU8, Ordering}, 8 | task::{Context, Poll}, 9 | time::Duration, 10 | }; 11 | 12 | use pac::RTC0; 13 | 14 | use crate::{time, NotSync}; 15 | 16 | const STEP: u32 = 4096; // 125 ms 17 | 18 | #[tasks::declare] 19 | mod task { 20 | use core::sync::atomic::{AtomicU16, Ordering}; 21 | 22 | use pac::RTC0; 23 | 24 | use crate::{led, Interrupt0}; 25 | 26 | use super::STEP; 27 | 28 | fn init() { 29 | RTC0::borrow_unchecked(|rtc| unsafe { 30 | rtc.INTENSET.write(|w| w.COMPARE0(1)); 31 | rtc.CC0.write(|w| w.COMPARE(super::STEP)); 32 | }); 33 | 34 | led::Red.on(); 35 | 36 | unsafe { crate::unmask0(&[Interrupt0::RTC0]) } 37 | } 38 | 39 | fn RTC0() { 40 | RTC0::borrow_unchecked(|rtc| { 41 | if rtc.EVENTS_OVRFLW.read().EVENTS_OVRFLW() != 0 { 42 | semidap::error!("RTC count overflowed ... aborting"); 43 | led::Blue.off(); 44 | led::Green.off(); 45 | led::Red.on(); 46 | semidap::abort(); 47 | } 48 | 49 | if rtc.EVENTS_COMPARE0.read().EVENTS_COMPARE() != 0 { 50 | static COUNT: AtomicU16 = AtomicU16::new(0); 51 | 52 | rtc.EVENTS_COMPARE0.zero(); 53 | let count = COUNT.load(Ordering::Relaxed).wrapping_add(1); 54 | rtc.CC0 55 | .write(|w| w.COMPARE(u32::from(count.wrapping_add(1)) * STEP)); 56 | COUNT.store(count, Ordering::Relaxed); 57 | 58 | match count % 12 { 59 | 0 => led::Red.on(), 60 | 1 => led::Red.off(), 61 | 2 => led::Red.on(), 62 | 3 => led::Red.off(), 63 | _ => {} 64 | } 65 | } 66 | 67 | if rtc.EVENTS_COMPARE1.read().EVENTS_COMPARE() != 0 { 68 | rtc.EVENTS_COMPARE1.zero(); 69 | rtc.INTENCLR.write(|w| w.COMPARE1(1)); 70 | } 71 | 72 | if rtc.EVENTS_COMPARE2.read().EVENTS_COMPARE() != 0 { 73 | rtc.EVENTS_COMPARE2.zero(); 74 | rtc.INTENCLR.write(|w| w.COMPARE2(1)); 75 | } 76 | 77 | if rtc.EVENTS_COMPARE3.read().EVENTS_COMPARE() != 0 { 78 | rtc.EVENTS_COMPARE3.zero(); 79 | rtc.INTENCLR.write(|w| w.COMPARE3(1)); 80 | } 81 | }); 82 | } 83 | } 84 | 85 | /// [Singleton] timer 86 | pub struct Timer { 87 | i: u8, 88 | // effectively owns the `RTC.CC*` register, which is `!Sync` 89 | _not_sync: NotSync, 90 | } 91 | 92 | // NOTE timer `0` is used for the "heartbeat" task 93 | static TAKEN: AtomicU8 = AtomicU8::new(1); 94 | 95 | impl Timer { 96 | /// Claims the `Timer` 97 | pub fn claim() -> Self { 98 | if TAKEN.load(Ordering::Relaxed) < 4 { 99 | let i = TAKEN.fetch_add(1, Ordering::Relaxed); 100 | 101 | if i < 4 { 102 | return Timer { 103 | i, 104 | _not_sync: NotSync::new(), 105 | }; 106 | } 107 | } 108 | 109 | semidap::panic!("no more `Timer` instances can be claimed") 110 | } 111 | 112 | /// Waits for the specified duration 113 | pub fn wait<'t>(&'t mut self, dur: Duration) -> impl Future + 't { 114 | let diff = dur.as_secs() as u32 * 32_768 115 | + dur 116 | .subsec_nanos() 117 | .wrapping_mul(4) 118 | .wrapping_div(5) 119 | .wrapping_mul(4) 120 | .wrapping_div(5) 121 | .wrapping_mul(4) 122 | .wrapping_div(78125); 123 | 124 | Wait { 125 | timer: self, 126 | state: State::NotStarted { 127 | diff: cmp::max(diff, 2), 128 | }, 129 | } 130 | } 131 | } 132 | 133 | struct Wait<'a> { 134 | timer: &'a mut Timer, 135 | state: State, 136 | } 137 | 138 | #[derive(Clone, Copy)] 139 | enum State { 140 | NotStarted { diff: u32 }, 141 | Started { end: u32 }, 142 | } 143 | 144 | impl Future for Wait<'_> { 145 | type Output = (); 146 | 147 | fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> { 148 | match self.state { 149 | State::NotStarted { diff } => { 150 | let i = self.timer.i; 151 | let end = time::now().wrapping_add(diff); 152 | RTC0::borrow_unchecked(|rtc| { 153 | // if i == 0 { 154 | // rtc.CC0.write(|w| w.COMPARE(end)) 155 | // } else 156 | if i == 1 { 157 | rtc.CC1.write(|w| w.COMPARE(end)); 158 | unsafe { rtc.INTENSET.write(|w| w.COMPARE1(1)) } 159 | } else if i == 2 { 160 | rtc.CC2.write(|w| w.COMPARE(end)); 161 | unsafe { rtc.INTENSET.write(|w| w.COMPARE2(1)) } 162 | } else { 163 | rtc.CC3.write(|w| w.COMPARE(end)); 164 | unsafe { rtc.INTENSET.write(|w| w.COMPARE3(1)) } 165 | } 166 | }); 167 | self.state = State::Started { end }; 168 | 169 | Poll::Pending 170 | } 171 | 172 | State::Started { end } => { 173 | if time::now() >= end { 174 | Poll::Ready(()) 175 | } else { 176 | Poll::Pending 177 | } 178 | } 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /firmware/hal/src/util.rs: -------------------------------------------------------------------------------- 1 | use core::ops; 2 | 3 | #[repr(align(4))] 4 | pub(crate) struct Align4(pub T); 5 | 6 | impl ops::Deref for Align4 { 7 | type Target = T; 8 | 9 | fn deref(&self) -> &T { 10 | &self.0 11 | } 12 | } 13 | 14 | impl ops::DerefMut for Align4 { 15 | fn deref_mut(&mut self) -> &mut T { 16 | &mut self.0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /firmware/pac/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "pac" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies.binfmt] 10 | optional = true 11 | path = "../../shared/binfmt" 12 | 13 | [features] 14 | CLOCK = [] 15 | FICR = [] 16 | P0 = [] 17 | POWER = [] 18 | RADIO = [] 19 | RTC0 = [] 20 | SPIM0 = [] 21 | USBD = [] 22 | # mainly used to generate docs 23 | all = [ 24 | "CLOCK", 25 | "FICR", 26 | "P0", 27 | "POWER", 28 | "RADIO", 29 | "RTC0", 30 | "SPIM0", 31 | "USBD", 32 | ] -------------------------------------------------------------------------------- /firmware/pac/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | compile_error!("run the `regen` tool before compiling this crate"); 4 | -------------------------------------------------------------------------------- /firmware/panic-abort/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "panic-abort" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | semidap = { path = "../semidap" } 11 | -------------------------------------------------------------------------------- /firmware/panic-abort/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[panic_handler] 4 | fn panic(_: &core::panic::PanicInfo) -> ! { 5 | semidap::abort() 6 | } 7 | -------------------------------------------------------------------------------- /firmware/pool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "pool" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | asm = { path = "../asm" } -------------------------------------------------------------------------------- /firmware/pool/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `async`-aware memory pool 2 | 3 | #![no_std] 4 | 5 | use core::{ 6 | mem, ops, 7 | ptr::{self, NonNull}, 8 | sync::atomic::{AtomicPtr, Ordering}, 9 | }; 10 | 11 | #[doc(hidden)] 12 | pub trait Pool: 'static { 13 | // keep things simple 14 | type T: Copy; 15 | 16 | #[doc(hidden)] 17 | fn get() -> &'static PoolImpl; 18 | } 19 | 20 | #[doc(hidden)] 21 | pub struct PoolImpl { 22 | head: AtomicPtr>, 23 | } 24 | 25 | impl PoolImpl { 26 | #[doc(hidden)] 27 | pub const fn new() -> Self { 28 | Self { 29 | head: AtomicPtr::new(ptr::null_mut()), 30 | } 31 | } 32 | 33 | #[doc(hidden)] 34 | pub fn pop(&self) -> Option>> { 35 | loop { 36 | let head = self.head.load(Ordering::Relaxed); 37 | if let Some(nn_head) = NonNull::new(head) { 38 | let next = unsafe { node_next(head).read() }; 39 | 40 | match self.head.compare_exchange_weak( 41 | head, 42 | next, 43 | Ordering::Relaxed, 44 | Ordering::Relaxed, 45 | ) { 46 | Ok(_) => break Some(nn_head), 47 | // interrupt occurred 48 | Err(_) => continue, 49 | } 50 | } else { 51 | // stack is observed as empty 52 | break None; 53 | } 54 | } 55 | } 56 | 57 | #[doc(hidden)] 58 | pub unsafe fn push(&self, new_head: NonNull>) { 59 | let mut head = self.head.load(Ordering::Relaxed); 60 | loop { 61 | node_next(new_head.as_ptr()).write(head); 62 | 63 | if let Err(p) = self.head.compare_exchange_weak( 64 | head, 65 | new_head.as_ptr(), 66 | Ordering::Relaxed, 67 | Ordering::Relaxed, 68 | ) { 69 | head = p 70 | } else { 71 | // memory block became available: wake up a task 72 | asm::sev(); 73 | return; 74 | } 75 | } 76 | } 77 | } 78 | 79 | /// Declares a memory pool 80 | #[macro_export] 81 | macro_rules! pool { 82 | ($(#[$attr:meta])* pub $ident:ident : [u8; $N:expr]) => { 83 | $(#[$attr])* 84 | pub struct $ident; 85 | 86 | impl $crate::Pool for $ident { 87 | type T = [u8; $N]; 88 | 89 | #[inline(always)] 90 | fn get() -> &'static $crate::PoolImpl<[u8; $N]> { 91 | static $ident: $crate::PoolImpl<[u8; $N]> = $crate::PoolImpl::new(); 92 | &$ident 93 | } 94 | } 95 | 96 | impl $ident { 97 | /// The size of the memory blocks managed by this pool 98 | pub const SIZE: usize = $N; 99 | 100 | /// Tries to acquire a memory block 101 | #[allow(dead_code)] 102 | pub fn try_alloc() -> Option<$crate::Box<$ident>> { 103 | unsafe { 104 | <$ident as $crate::Pool>::get() 105 | .pop() 106 | .map(|n| $crate::Box::from_raw(n.as_ptr())) 107 | } 108 | } 109 | 110 | /// Acquires a memory block 111 | #[allow(dead_code)] 112 | pub fn alloc() -> impl core::future::Future> { 113 | struct Alloc; 114 | 115 | impl core::future::Future for Alloc { 116 | type Output = $crate::Box<$ident>; 117 | 118 | fn poll( 119 | self: core::pin::Pin<&mut Self>, 120 | _: &mut core::task::Context, 121 | ) -> core::task::Poll { 122 | $ident::try_alloc() 123 | .map(core::task::Poll::Ready) 124 | .unwrap_or(core::task::Poll::Pending) 125 | } 126 | } 127 | 128 | Alloc 129 | } 130 | 131 | /// Gives the pool a memory block to manage 132 | #[allow(dead_code)] 133 | pub fn manage(node: &'static mut core::mem::MaybeUninit<$crate::Node<[u8; $N]>>) { 134 | unsafe { 135 | <$ident as $crate::Pool>::get().push(core::ptr::NonNull::from(node).cast()) 136 | } 137 | } 138 | } 139 | }; 140 | } 141 | 142 | /// Owning pointer 143 | pub struct Box

144 | where 145 | P: Pool, 146 | { 147 | node: NonNull>, 148 | } 149 | 150 | impl

Box

151 | where 152 | P: Pool, 153 | { 154 | /// Consumes the `Box`, returning a raw pointer 155 | /// 156 | /// # Safety 157 | /// 158 | /// See `alloc::boxed::Box` safety requirements 159 | pub unsafe fn from_raw(raw: *mut Node) -> Self { 160 | Box { 161 | node: NonNull::new_unchecked(raw), 162 | } 163 | } 164 | 165 | /// Consumes the `Box`, returning a raw pointer 166 | pub fn into_raw(self) -> *mut Node { 167 | let node = self.node; 168 | mem::forget(self); 169 | node.as_ptr() 170 | } 171 | } 172 | 173 | unsafe impl

Send for Box

174 | where 175 | P: Pool, 176 | P::T: Send, 177 | { 178 | } 179 | 180 | unsafe impl

Sync for Box

181 | where 182 | P: Pool, 183 | P::T: Sync, 184 | { 185 | } 186 | 187 | impl

ops::Deref for Box

188 | where 189 | P: Pool, 190 | { 191 | type Target = P::T; 192 | 193 | fn deref(&self) -> &P::T { 194 | unsafe { &*node_data(self.node.as_ptr()) } 195 | } 196 | } 197 | 198 | impl

ops::DerefMut for Box

199 | where 200 | P: Pool, 201 | { 202 | fn deref_mut(&mut self) -> &mut P::T { 203 | unsafe { &mut *node_data(self.node.as_ptr()) } 204 | } 205 | } 206 | 207 | impl

Drop for Box

208 | where 209 | P: Pool, 210 | { 211 | fn drop(&mut self) { 212 | unsafe { P::get().push(self.node) } 213 | } 214 | } 215 | 216 | #[cfg_attr(target_pointer_width = "32", repr(align(4)))] 217 | #[cfg_attr(target_pointer_width = "64", repr(align(8)))] 218 | #[repr(C)] 219 | pub struct Node { 220 | data: T, 221 | } 222 | 223 | fn node_data(node: *mut Node) -> *mut T { 224 | node.cast() 225 | } 226 | 227 | fn node_next(node: *mut Node) -> *mut *mut Node { 228 | node.cast() 229 | } 230 | 231 | #[cfg(test)] 232 | mod tests { 233 | use core::mem::MaybeUninit; 234 | 235 | use super::Node; 236 | 237 | #[test] 238 | fn empty() { 239 | pool!(pub A: [u8; 1]); 240 | 241 | assert!(A::try_alloc().is_none()); 242 | } 243 | 244 | #[test] 245 | fn sanity() { 246 | static mut N: MaybeUninit> = MaybeUninit::uninit(); 247 | 248 | pool!(pub B: [u8; 1]); 249 | B::manage(unsafe { &mut N }); 250 | 251 | let x = B::try_alloc().unwrap(); 252 | assert!(B::try_alloc().is_none()); // no more memory blocks in the pool 253 | drop(x); // returns to the pool 254 | 255 | let y = B::try_alloc().unwrap(); // can claim a memory block again 256 | assert!(B::try_alloc().is_none()); 257 | core::mem::forget(y); 258 | assert!(B::try_alloc().is_none()); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /firmware/ring/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "ring" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | asm = { path = "../asm" } 11 | -------------------------------------------------------------------------------- /firmware/ring/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! async-aware circular buffer 2 | 3 | #![no_std] 4 | 5 | use core::{ 6 | cmp, ptr, 7 | sync::atomic::{self, AtomicU16, Ordering}, 8 | }; 9 | 10 | pub const N: u16 = 256; 11 | 12 | pub struct Buffer { 13 | buffer: *mut u8, 14 | read: AtomicU16, 15 | write: AtomicU16, 16 | } 17 | 18 | unsafe impl Sync for Buffer {} 19 | 20 | impl Buffer { 21 | /// # Safety 22 | /// 23 | /// This is a single-producer single-consumer interrupt-safe circular buffer. All producer 24 | /// (`write`) operations need be kept in a single execution context (interrupt priority); the 25 | /// same requirement applies to the consumer (`read`) operations. Because `Buffer` can be placed 26 | /// in a `static` variable this requirement needs to be enforced manually 27 | pub const unsafe fn new(buffer: *const [u8; N as usize]) -> Self { 28 | Self { 29 | buffer: buffer as *mut u8, 30 | read: AtomicU16::new(0), 31 | write: AtomicU16::new(0), 32 | } 33 | } 34 | 35 | pub fn bytes_to_read(&self) -> usize { 36 | let read = self.read.load(Ordering::Relaxed); 37 | let write = self.write.load(Ordering::Relaxed); 38 | write.wrapping_sub(read).into() 39 | } 40 | 41 | pub fn read(&self, buf: &mut [u8]) -> usize { 42 | if buf.is_empty() { 43 | return 0; 44 | } 45 | 46 | let read = self.read.load(Ordering::Relaxed); 47 | let write = self.write.load(Ordering::Relaxed); 48 | atomic::compiler_fence(Ordering::Acquire); 49 | 50 | let available = write.wrapping_sub(read); 51 | if available == 0 { 52 | return 0; 53 | } 54 | 55 | let cursor = read % N; 56 | let len = cmp::min(buf.len(), available as usize) as u16; 57 | unsafe { 58 | if cursor + len > N { 59 | // split memcpy 60 | let pivot = N - cursor; 61 | ptr::copy_nonoverlapping( 62 | self.buffer.add(cursor.into()), 63 | buf.as_mut_ptr(), 64 | pivot.into(), 65 | ); 66 | ptr::copy_nonoverlapping( 67 | self.buffer, 68 | buf.as_mut_ptr().add(pivot.into()), 69 | (len - pivot).into(), 70 | ); 71 | } else { 72 | // single memcpy 73 | ptr::copy_nonoverlapping( 74 | self.buffer.add(cursor.into()), 75 | buf.as_mut_ptr(), 76 | len.into(), 77 | ); 78 | } 79 | } 80 | atomic::compiler_fence(Ordering::Release); 81 | self.read.store(read.wrapping_add(len), Ordering::Relaxed); 82 | len.into() 83 | } 84 | 85 | #[cfg(TODO)] 86 | pub async fn write_all(&self, bytes: &[u8]) { 87 | todo!() 88 | } 89 | 90 | pub fn write(&self, bytes: &[u8]) -> usize { 91 | if bytes.is_empty() { 92 | return 0; 93 | } 94 | 95 | let write = self.write.load(Ordering::Relaxed); 96 | let read = self.read.load(Ordering::Relaxed); 97 | atomic::compiler_fence(Ordering::Acquire); 98 | 99 | let available = read.wrapping_add(N).wrapping_sub(write); 100 | if available == 0 { 101 | return 0; 102 | } 103 | 104 | let cursor = write % N; 105 | let len = cmp::min(bytes.len(), available.into()) as u16; 106 | unsafe { 107 | if cursor + len > N { 108 | // split memcpy 109 | let pivot = N - cursor; 110 | ptr::copy_nonoverlapping( 111 | bytes.as_ptr(), 112 | self.buffer.add(cursor.into()), 113 | pivot.into(), 114 | ); 115 | ptr::copy_nonoverlapping( 116 | bytes.as_ptr().add(pivot.into()), 117 | self.buffer, 118 | (len - pivot).into(), 119 | ); 120 | } else { 121 | // single memcpy 122 | ptr::copy_nonoverlapping( 123 | bytes.as_ptr(), 124 | self.buffer.add(cursor.into()), 125 | len.into(), 126 | ); 127 | } 128 | } 129 | atomic::compiler_fence(Ordering::Release); 130 | self.write.store(write.wrapping_add(len), Ordering::Relaxed); 131 | len.into() 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /firmware/semidap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "semidap" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | binfmt = { path = "../../shared/binfmt" } 11 | proc-macro-hack = "0.5.11" 12 | proc-macro-nested = "0.1.3" 13 | -------------------------------------------------------------------------------- /firmware/semidap/README.md: -------------------------------------------------------------------------------- 1 | # `semidap` 🦗 2 | 3 | > CMSIS-DAP based semihosting 4 | 5 | This is the target library. More information can be found in the README of the 6 | host side tool (`/host/semidap`). 7 | -------------------------------------------------------------------------------- /firmware/semidap/asm.s: -------------------------------------------------------------------------------- 1 | /* fn DefaultHandler() -> ! */ 2 | .global DefaultHandler 3 | .cfi_sections .debug_frame 4 | .section .text.DefaultHandler, "ax" 5 | .thumb_func 6 | .cfi_startproc 7 | DefaultHandler: 8 | bkpt 0xff 9 | .cfi_endproc 10 | .size DefaultHandler, . - DefaultHandler 11 | 12 | /* fn __exit(r0: i32) -> ! */ 13 | .global __exit 14 | .cfi_sections .debug_frame 15 | .section .text.__exit, "ax" 16 | .thumb_func 17 | .cfi_startproc 18 | __exit: 19 | bkpt 0xab 20 | .cfi_endproc 21 | .size __exit, . - __exit 22 | 23 | /* fn __abort() -> ! */ 24 | .global __abort 25 | .cfi_sections .debug_frame 26 | .section .text.__abort, "ax" 27 | .thumb_func 28 | .cfi_startproc 29 | __abort: 30 | bkpt 0xaa 31 | .cfi_endproc 32 | .size __abort, . - __abort 33 | -------------------------------------------------------------------------------- /firmware/semidap/assemble.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | local pkg_name=semidap 5 | 6 | arm-none-eabi-as -march=armv7e-m asm.s -o bin/$pkg_name.o 7 | ar crs bin/thumbv7em-none-eabi.a bin/$pkg_name.o 8 | 9 | rm bin/*.o 10 | } 11 | 12 | main 13 | -------------------------------------------------------------------------------- /firmware/semidap/bin/thumbv7em-none-eabi.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japaric/embedded2020/da43017e25ea2d3df11ec1e4ecf8e77d60868038/firmware/semidap/bin/thumbv7em-none-eabi.a -------------------------------------------------------------------------------- /firmware/semidap/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let out_dir = &PathBuf::from(env::var("OUT_DIR")?); 5 | let pkg_name = env::var("CARGO_PKG_NAME")?; 6 | let target = env::var("TARGET")?; 7 | 8 | // place the pre-compiled assembly somewhere the linker can find it 9 | fs::copy( 10 | format!("bin/{}.a", target), 11 | out_dir.join(format!("lib{}.a", pkg_name)), 12 | )?; 13 | println!("cargo:rustc-link-lib=static={}", pkg_name); 14 | 15 | println!("cargo:rustc-link-search={}", out_dir.display()); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /firmware/semidap/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! CMSIS-DAP based semihosting 2 | 3 | #![deny(missing_docs)] 4 | #![deny(warnings)] 5 | #![no_std] 6 | 7 | use core::{ 8 | cell::{Cell, UnsafeCell}, 9 | mem::MaybeUninit, 10 | }; 11 | 12 | #[doc(hidden)] 13 | pub use binfmt::{binWrite, binwrite, Level}; 14 | 15 | /// Logs the formatted string at the `Debug` log level 16 | /// 17 | /// A newline will be appended to the end of the format string 18 | #[macro_export] 19 | macro_rules! debug { 20 | ($($tt:tt)+) => { 21 | match () { 22 | #[cfg(debug_assertions)] 23 | () => { 24 | match $crate::stdout() { 25 | ref mut __stdout__ => { 26 | $crate::log(__stdout__, $crate::Level::Debug); 27 | $crate::binwrite!(__stdout__, $($tt)+) 28 | } 29 | } 30 | } 31 | #[cfg(not(debug_assertions))] 32 | () => {} 33 | } 34 | } 35 | } 36 | 37 | /// Logs the formatted string at the `Error` log level 38 | /// 39 | /// A newline will be appended to the end of the format string 40 | #[macro_export] 41 | macro_rules! error { 42 | ($($tt:tt)+) => { 43 | match $crate::stdout() { 44 | ref mut __stdout__ => { 45 | $crate::log(__stdout__, $crate::Level::Error); 46 | $crate::binwrite!(__stdout__, $($tt)+) 47 | } 48 | } 49 | } 50 | } 51 | 52 | /// Logs the formatted string at the `Info` log level 53 | /// 54 | /// A newline will be appended to the end of the format string 55 | #[macro_export] 56 | macro_rules! info { 57 | ($($tt:tt)+) => { 58 | match $crate::stdout() { 59 | ref mut __stdout__ => { 60 | $crate::log(__stdout__, $crate::Level::Info); 61 | $crate::binwrite!(__stdout__, $($tt)+) 62 | } 63 | } 64 | } 65 | } 66 | 67 | /// Logs the formatted string at the `Trace` log level 68 | /// 69 | /// A newline will be appended to the end of the format string 70 | #[macro_export] 71 | macro_rules! trace { 72 | ($($tt:tt)+) => { 73 | match () { 74 | #[cfg(debug_assertions)] 75 | () => { 76 | match $crate::stdout() { 77 | ref mut __stdout__ => { 78 | $crate::log(__stdout__, $crate::Level::Trace); 79 | $crate::binwrite!(__stdout__, $($tt)+) 80 | } 81 | } 82 | } 83 | #[cfg(not(debug_assertions))] 84 | () => {} 85 | } 86 | } 87 | } 88 | 89 | /// Logs the formatted string at the `Warn` log level 90 | /// 91 | /// A newline will be appended to the end of the format string 92 | #[macro_export] 93 | macro_rules! warn { 94 | ($($tt:tt)+) => { 95 | match $crate::stdout() { 96 | ref mut __stdout__ => { 97 | $crate::log(__stdout__, $crate::Level::Warn); 98 | $crate::binwrite!(__stdout__, $($tt)+) 99 | } 100 | } 101 | } 102 | } 103 | 104 | fn in_thread_mode() -> bool { 105 | const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; 106 | 107 | unsafe { SCB_ICSR.read_volatile() as u8 == 0 } 108 | } 109 | 110 | #[doc(hidden)] 111 | pub fn log(stdout: &mut impl binWrite, level: Level) { 112 | extern "Rust" { 113 | fn __semidap_timestamp() -> u32; 114 | } 115 | let ts = unsafe { __semidap_timestamp() }; 116 | stdout.write_byte(level as u8); 117 | stdout.leb128_write(ts); 118 | } 119 | 120 | /// Aborts the `semidap` process running on the host 121 | #[inline(always)] 122 | pub fn abort() -> ! { 123 | extern "C" { 124 | fn __abort() -> !; 125 | } 126 | unsafe { __abort() } 127 | } 128 | 129 | /// Exits the `semidap` process running on the host with the specified exit code 130 | #[inline(always)] 131 | pub fn exit(code: i32) -> ! { 132 | extern "C" { 133 | fn __exit(r0: i32) -> !; 134 | } 135 | unsafe { __exit(code) } 136 | } 137 | 138 | #[doc(hidden)] 139 | #[derive(Clone, Copy)] 140 | pub struct Channel { 141 | // the host never modifies these fields 142 | bufferp: *mut u8, 143 | write: &'static Cell, 144 | // NOTE the `read` pointer is maintained in host memory 145 | } 146 | 147 | /// Implementation detail 148 | /// # Safety 149 | /// None of `Channel` methods are re-entrant safe 150 | #[doc(hidden)] 151 | pub fn stdout() -> Channel { 152 | if in_thread_mode() { 153 | unsafe { CHANNELS[0] } 154 | } else { 155 | // TODO one channel per priority level 156 | unsafe { CHANNELS[1] } 157 | } 158 | } 159 | 160 | // NOTE per HID transaction the host will drain 37-48B from the circular buffer (assuming 64B HID 161 | // packets) 162 | // NOTE for performance `CAPACITY` should be a power of 2 163 | const CAPACITY: u16 = 4096; 164 | 165 | #[no_mangle] 166 | static mut SEMIDAP_CURSOR: [Cell; 2] = [Cell::new(0), Cell::new(0)]; 167 | // NOTE buffers must be aligned so that they never span over a 4KB address boundary 168 | // for example we don't want a buffer with this address range: `0x2000_0fe0..0x2000_1020` 169 | #[repr(align(4096))] 170 | struct Align(T); 171 | #[link_section = ".uninit.SEMIDAP_BUFFER"] 172 | #[no_mangle] 173 | static mut SEMIDAP_BUFFER: [UnsafeCell>>; 2] = [ 174 | UnsafeCell::new(MaybeUninit::uninit()), 175 | UnsafeCell::new(MaybeUninit::uninit()), 176 | ]; 177 | 178 | static mut CHANNELS: [Channel; 2] = unsafe { 179 | [ 180 | Channel { 181 | write: &SEMIDAP_CURSOR[0], 182 | bufferp: &SEMIDAP_BUFFER[0] as *const _ as *mut u8, 183 | }, 184 | Channel { 185 | write: &SEMIDAP_CURSOR[1], 186 | bufferp: &SEMIDAP_BUFFER[1] as *const _ as *mut u8, 187 | }, 188 | ] 189 | }; 190 | 191 | impl Channel { 192 | fn push(&self, byte: u8) { 193 | let write = self.write.get(); 194 | let cursor = write % CAPACITY; 195 | unsafe { self.bufferp.add(cursor.into()).write(byte) } 196 | self.write.set(write.wrapping_add(1)); 197 | } 198 | 199 | fn extend_from_slice(&self, bytes: &[u8]) { 200 | // NOTE we assume that `bytes.len` is less than `u16::max_value` which 201 | // is very likely to be the case as logs are compressed 202 | let len = bytes.len() as u16; 203 | let write = self.write.get(); 204 | let cursor = write % CAPACITY; 205 | 206 | // NOTE it might be worth the do writes in `HID_PACKET_SIZE` chunks to 207 | // improve the chances of the host advancing its `read` pointer during 208 | // the execution of this method. OTOH, it's very unlikely that 209 | // `bytes.len()` will be greater than `HID_PACKET_SIZE` 210 | if cursor + len > CAPACITY { 211 | // split memcpy 212 | // NOTE here we assume that `bytes.len()` is less than `CAPACITY`. 213 | // When that's not the case the second `memcpy` could result in an 214 | // out of bounds write. It's very unlikely that `bytes.len()` will 215 | // ever be greater than `CAPACITY` because logs are compressed 216 | let pivot = CAPACITY.wrapping_sub(cursor); 217 | unsafe { 218 | memcpy( 219 | bytes.as_ptr(), 220 | self.bufferp.add(cursor.into()), 221 | pivot.into(), 222 | ); 223 | memcpy( 224 | bytes.as_ptr().add(pivot.into()), 225 | self.bufferp, 226 | (len - pivot).into(), 227 | ); 228 | } 229 | } else { 230 | // single memcpy 231 | unsafe { memcpy(bytes.as_ptr(), self.bufferp.add(cursor.into()), len.into()) } 232 | } 233 | 234 | // NOTE we want the `write` cursor to always be updated after `bufferp`. 235 | // we may need a compiler barrier here 236 | self.write.set(write.wrapping_add(len)); 237 | } 238 | } 239 | 240 | impl binfmt::binWrite for Channel { 241 | fn write_byte(&mut self, byte: u8) { 242 | self.push(byte) 243 | } 244 | 245 | fn write(&mut self, bytes: &[u8]) { 246 | self.extend_from_slice(bytes) 247 | } 248 | } 249 | 250 | // opt-level = 3 251 | #[cfg(not(debug_assertions))] 252 | unsafe fn memcpy(src: *const u8, dst: *mut u8, len: usize) { 253 | // lowers to `__aebi_memcpy` 254 | core::ptr::copy_nonoverlapping(src, dst, len); 255 | } 256 | 257 | // opt-level = 'z' 258 | #[cfg(debug_assertions)] 259 | unsafe fn memcpy(mut src: *const u8, mut dst: *mut u8, len: usize) { 260 | // lowers to a tight loop (less instructions than `__aeabi_memcpy`) 261 | for _ in 0..len { 262 | dst.write_volatile(src.read_volatile()); 263 | dst = dst.add(1); 264 | src = src.add(1); 265 | } 266 | } 267 | 268 | /// Checks the condition and aborts the program if it evaluates to `false` 269 | #[macro_export] 270 | macro_rules! assert { 271 | ($e:expr, $($tt:tt)*) => { 272 | if !$e { 273 | $crate::panic!($($tt)*) 274 | } 275 | }; 276 | } 277 | 278 | /// Prints an `Error` message and aborts the program 279 | #[macro_export] 280 | macro_rules! panic { 281 | () => { 282 | $crate::abort() 283 | }; 284 | 285 | ($($tt:tt)*) => {{ 286 | $crate::error!($($tt)*); 287 | $crate::abort() 288 | }} 289 | } 290 | -------------------------------------------------------------------------------- /firmware/tasks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "tasks" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | tasks-macros = { path = "../../host/tasks-macros" } -------------------------------------------------------------------------------- /firmware/tasks/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub use tasks_macros::declare; 4 | -------------------------------------------------------------------------------- /firmware/tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "tests" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | asm = { path = "../asm" } 11 | async-core = { path = "../async-core" } 12 | binfmt = { path = "../../shared/binfmt" } 13 | cm = { path = "../../shared/cm", features = ["binfmt", "SCB"] } 14 | executor = { path = "../executor" } 15 | hal = { path = "../hal" } 16 | pac = { path = "../pac" } 17 | panic-abort = { path = "../panic-abort" } 18 | panic-never = "0.1.0" 19 | semidap = { path = "../semidap" } 20 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/assert.rs: -------------------------------------------------------------------------------- 1 | //! (test) Stack backtrace across nested exceptions 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use hal as _; // memory layout 7 | use panic_never as _; // this program contains zero core::panic* calls 8 | 9 | #[no_mangle] 10 | fn main() -> ! { 11 | semidap::assert!(false, "assertion failed"); 12 | 13 | semidap::exit(0) 14 | } 15 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/async-channel.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use async_core::{task, unsync::spsc::Channel}; 5 | use hal as _; // memory layout 6 | use panic_never as _; // this program contains zero core::panic* calls 7 | 8 | #[no_mangle] 9 | fn main() -> ! { 10 | let mut ch = Channel::new(); 11 | let (mut s, mut r) = ch.split(); 12 | 13 | let a = async { 14 | semidap::info!("A: before recv"); 15 | let m = r.recv().await; 16 | semidap::info!("A: received `{}`", m); 17 | 18 | semidap::info!("DONE"); 19 | semidap::exit(0) 20 | }; 21 | 22 | let b = async { 23 | semidap::info!("B: before send"); 24 | s.send(42).await; 25 | semidap::info!("B: after send"); 26 | 27 | loop { 28 | semidap::info!("B: yield"); 29 | task::r#yield().await; 30 | } 31 | }; 32 | 33 | executor::run!(a, b) 34 | } 35 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/async-mutex.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use async_core::{task, unsync::Mutex}; 5 | use hal as _; // memory layout 6 | use panic_never as _; // this program contains zero core::panic* calls 7 | 8 | #[no_mangle] 9 | fn main() -> ! { 10 | let m = Mutex::new(0); 11 | 12 | let mut lock = m.try_lock().unwrap(); 13 | 14 | let a = async { 15 | semidap::info!("A: before lock"); 16 | let lock = m.lock().await; 17 | semidap::info!("A: after lock"); 18 | 19 | semidap::info!("A: {}", *lock); 20 | 21 | semidap::info!("DONE"); 22 | 23 | semidap::exit(0) 24 | }; 25 | 26 | let b = async { 27 | semidap::info!("B: before write"); 28 | *lock = 42; 29 | drop(lock); 30 | 31 | semidap::info!("B: after releasing the lock"); 32 | 33 | loop { 34 | semidap::info!("B: yield"); 35 | task::r#yield().await; 36 | } 37 | }; 38 | 39 | executor::run!(a, b) 40 | } 41 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/async-yield.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use async_core::task; 5 | use hal as _; // memory layout 6 | use panic_never as _; // this program contains zero core::panic* calls 7 | 8 | #[no_mangle] 9 | fn main() -> ! { 10 | let a = async { 11 | semidap::info!("before yield"); 12 | task::r#yield().await; 13 | semidap::info!("after yield"); 14 | semidap::exit(0) 15 | }; 16 | 17 | executor::run!(a) 18 | } 19 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/binfmt.rs: -------------------------------------------------------------------------------- 1 | //! Binary formatting registers 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use hal as _; // memory layout 7 | use panic_never as _; // this program contains zero core::panic* calls 8 | 9 | #[no_mangle] 10 | fn main() -> ! { 11 | let a = hal::cyccnt(); 12 | 13 | cm::SCB::borrow_unchecked(|scb| { 14 | semidap::info!("{}", scb.AIRCR.read()); 15 | }); 16 | 17 | let b = hal::cyccnt(); 18 | 19 | semidap::info!("That took {} cycles", b - a); 20 | 21 | semidap::exit(0) 22 | } 23 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/exception.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cm::SCB; 5 | use hal as _; // memory layout 6 | use panic_never as _; // this program contains zero core::panic* calls 7 | 8 | #[no_mangle] 9 | fn main() -> ! { 10 | semidap::info!("thread mode"); 11 | foo(); 12 | 13 | // trigger `PendSV` 14 | SCB::borrow_unchecked(|scb| scb.ICSR.rmw(|_, w| w.PENDSVSET(1))); 15 | 16 | semidap::info!("DONE"); 17 | 18 | semidap::exit(0) 19 | } 20 | 21 | #[allow(non_snake_case)] 22 | #[no_mangle] 23 | fn PendSV() { 24 | semidap::info!("interrupt context"); 25 | foo(); 26 | } 27 | 28 | fn foo() { 29 | semidap::info!("foo"); 30 | } 31 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/nested.rs: -------------------------------------------------------------------------------- 1 | //! (test) Stack backtrace across nested exceptions 2 | 3 | #![no_main] 4 | #![no_std] 5 | 6 | use cm::SCB; 7 | use hal as _; // memory layout 8 | use panic_never as _; // this program contains zero core::panic* calls 9 | 10 | #[no_mangle] 11 | fn main() -> ! { 12 | // pend `PendSV` 13 | SCB::borrow_unchecked(|scb| scb.ICSR.rmw(|_r, w| w.PENDSVSET(1))); 14 | 15 | use_the_stack(); 16 | 17 | semidap::exit(0) 18 | } 19 | 20 | #[allow(non_snake_case)] 21 | #[no_mangle] 22 | fn PendSV() { 23 | use_the_stack(); 24 | 25 | foo(); 26 | } 27 | 28 | #[inline(never)] 29 | fn foo() { 30 | // pend `NMI` 31 | SCB::borrow_unchecked(|scb| scb.ICSR.rmw(|_r, w| w.NMIPENDSET(1))); 32 | 33 | use_the_stack(); 34 | } 35 | 36 | #[allow(non_snake_case)] 37 | #[no_mangle] 38 | fn NMI() { 39 | semidap::abort() 40 | } 41 | 42 | #[inline(always)] 43 | fn use_the_stack() { 44 | let mut x = 0; 45 | let y = &mut x as *mut i32; 46 | unsafe { 47 | (&y as *const *mut i32).read_volatile(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/panic.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use hal as _; // memory layout 5 | use panic_never as _; // this program contains zero core::panic* calls 6 | 7 | #[no_mangle] 8 | fn main() -> ! { 9 | semidap::panic!("bye") 10 | } 11 | -------------------------------------------------------------------------------- /firmware/tests/src/bin/wfe.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cm::SCB; 5 | use hal as _; // memory layout 6 | use panic_never as _; // this program contains zero core::panic* calls 7 | 8 | #[no_mangle] 9 | fn main() -> ! { 10 | semidap::trace!("A"); 11 | 12 | // trigger `PendSV` 13 | SCB::borrow_unchecked(|scb| scb.ICSR.rmw(|_, w| w.PENDSVSET(1))); 14 | 15 | semidap::trace!("B"); 16 | 17 | asm::wfe(); 18 | 19 | semidap::trace!("C"); 20 | 21 | semidap::exit(0) 22 | } 23 | 24 | #[allow(non_snake_case)] 25 | #[no_mangle] 26 | fn PendSV() { 27 | semidap::trace!("exception"); 28 | } 29 | -------------------------------------------------------------------------------- /host/.gitignore: -------------------------------------------------------------------------------- 1 | *.svd 2 | /target 3 | Cargo.lock -------------------------------------------------------------------------------- /host/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "acm-cat", 4 | "binfmt-parser", 5 | "cmsis-dap", 6 | "executor-macros", 7 | "hidc", 8 | "regen", 9 | "semidap", 10 | "semiprobe", 11 | "tasks-macros", 12 | ] -------------------------------------------------------------------------------- /host/README.md: -------------------------------------------------------------------------------- 1 | # `host` 2 | 3 | Code that's meant to be compiled for the host and not the target. 4 | -------------------------------------------------------------------------------- /host/acm-cat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "acm-cat" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | anyhow = "1.0.31" 11 | consts = { path = "../../shared/consts" } 12 | serialport = "3.3.0" 13 | -------------------------------------------------------------------------------- /host/acm-cat/src/main.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | use std::{ 3 | io::{self, Read as _, Write as _}, 4 | thread, 5 | }; 6 | 7 | use anyhow::anyhow; 8 | use serialport::SerialPortType; 9 | 10 | fn main() -> Result<(), anyhow::Error> { 11 | let dev = serialport::available_ports()? 12 | .into_iter() 13 | .filter(|info| match info.port_type { 14 | SerialPortType::UsbPort(ref port) => port.vid == consts::VID, 15 | _ => false, 16 | }) 17 | .next() 18 | .ok_or_else(|| anyhow!("device not found"))?; 19 | 20 | let mut port = serialport::open(&dev.port_name)?; 21 | 22 | let stdout = io::stdout(); 23 | let mut stdout = stdout.lock(); 24 | let mut buf = [0; 64]; 25 | loop { 26 | if port.bytes_to_read()? != 0 { 27 | let n = port.read(&mut buf)?; 28 | stdout.write(&buf[..n])?; 29 | } else { 30 | thread::sleep(Duration::from_millis(1)) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /host/binfmt-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "binfmt-parser" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | colored = "1.9.3" 11 | binfmt = { path = "../../shared/binfmt" } 12 | -------------------------------------------------------------------------------- /host/cmsis-dap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "cmsis-dap" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = "1.0.26" 13 | arrayref = "0.3.6" 14 | hidapi = "1.1.1" 15 | log = "0.4.8" 16 | cm = { path = "../../shared/cm", features = ["DCB", "SCB"] } 17 | -------------------------------------------------------------------------------- /host/cmsis-dap/src/adiv5.rs: -------------------------------------------------------------------------------- 1 | //! ARM Debug Interface v5 2 | 3 | use crate::dap; 4 | 5 | /// ADIv5 registers 6 | #[allow(non_camel_case_types)] 7 | #[derive(Clone, Copy, Debug)] 8 | pub enum Register { 9 | /// Debug Port Identification Register 10 | DP_DPIDR, 11 | /// DP Control register 12 | DP_CTRL, 13 | 14 | /// DP Status register 15 | DP_STAT, 16 | 17 | /// AP Select Register 18 | DP_SELECT, 19 | 20 | /// Read Buffer 21 | DP_RDBUFF, 22 | 23 | /// Control/Status Word 24 | AHB_AP_CSW, 25 | 26 | /// Transfer Address 27 | AHB_AP_TAR, 28 | 29 | /// Data Read/Write 30 | AHB_AP_DRW, 31 | 32 | /// Banked Data 0 33 | AHB_AP_BD0, 34 | 35 | /// Banked Data 1 36 | AHB_AP_BD1, 37 | 38 | /// Banked Data 2 39 | AHB_AP_BD2, 40 | 41 | /// Banked Data 3 42 | AHB_AP_BD3, 43 | } 44 | 45 | impl Register { 46 | pub(crate) fn banked_data(addr: u32) -> Self { 47 | assert_eq!(addr % 4, 0, "address not 4-byte aligned"); 48 | 49 | match addr & 0xf { 50 | 0x0 => Register::AHB_AP_BD0, 51 | 0x4 => Register::AHB_AP_BD1, 52 | 0x8 => Register::AHB_AP_BD2, 53 | 0xc => Register::AHB_AP_BD3, 54 | _ => unreachable!(), 55 | } 56 | } 57 | 58 | pub(crate) fn ap_bank(self) -> Option { 59 | match self { 60 | Register::DP_DPIDR 61 | | Register::DP_CTRL 62 | | Register::DP_STAT 63 | | Register::DP_SELECT 64 | | Register::DP_RDBUFF => None, 65 | 66 | Register::AHB_AP_CSW | Register::AHB_AP_TAR | Register::AHB_AP_DRW => { 67 | Some(ApBank::AHB_AP(0)) 68 | } 69 | 70 | Register::AHB_AP_BD0 71 | | Register::AHB_AP_BD1 72 | | Register::AHB_AP_BD2 73 | | Register::AHB_AP_BD3 => Some(ApBank::AHB_AP(1)), 74 | } 75 | } 76 | } 77 | 78 | /// AP bank 79 | #[allow(non_camel_case_types)] 80 | #[derive(Clone, Copy, PartialEq)] 81 | pub(crate) enum ApBank { 82 | AHB_AP(u8), 83 | } 84 | 85 | impl crate::Dap { 86 | /// [ADIv5] Reads the specified ADIv5 `register` 87 | /// 88 | /// # Panics 89 | /// 90 | /// This method panics if there are outstanding DAP transfer requests -- `execute_dap_transfer` 91 | /// must be called before calling this method 92 | pub fn read_register(&mut self, reg: Register) -> Result { 93 | assert_eq!(self.total_requests, 0, "outstanding DAP transfer requests"); 94 | self.push_dap_transfer_request(reg, dap::Request::Read); 95 | Ok(self.execute_dap_transfer()?[0]) 96 | } 97 | 98 | /// [ADIv5] Writes the given `value` to the specified ADIv5 `register` 99 | /// 100 | /// # Panics 101 | /// 102 | /// This method panics if there are outstanding DAP transfer requests -- `execute_dap_transfer` 103 | /// must be called before calling this method 104 | pub fn write_register(&mut self, reg: Register, val: u32) -> Result<(), anyhow::Error> { 105 | assert_eq!(self.total_requests, 0, "outstanding DAP transfer requests"); 106 | self.push_dap_transfer_request(reg, dap::Request::Write(val)); 107 | self.execute_dap_transfer().map(drop) 108 | } 109 | } 110 | 111 | /* # Register Bit fields */ 112 | 113 | /* ## DP access port */ 114 | 115 | /* ### DPIDR register */ 116 | 117 | /// Reserved field 118 | pub const DP_DPIDR_RESERVED: u32 = 1; 119 | 120 | /// JEDEC Manufacturer ID 121 | pub const DP_DPIDR_MANUFACTURER_ARM: u32 = 0x23b << 1; 122 | 123 | /* ### STAT / CTRL register */ 124 | 125 | /// System power-up acknowledge 126 | pub const DP_STAT_CSYSPWRUPACK: u32 = 1 << 31; 127 | 128 | /// System power-up request 129 | pub const DP_CTRL_CSYSPWRUPREQ: u32 = 1 << 30; 130 | 131 | /// Debug power-up acknowledge 132 | pub const DP_STAT_CDBGPWRUPACK: u32 = 1 << 29; 133 | 134 | /// Debug power-up request 135 | pub const DP_CTRL_CDBGPWRUPREQ: u32 = 1 << 28; 136 | 137 | /* ### SELECT register */ 138 | 139 | /// APSEL = AHB-AP 140 | pub const DP_SELECT_APSEL_AHB_AP: u32 = 0x00 << 24; 141 | 142 | /// Offset of the APBANKSEL field 143 | pub const DP_SELECT_APBANKSEL_OFFSET: u8 = 4; 144 | 145 | /* ## AHB-AP access port */ 146 | -------------------------------------------------------------------------------- /host/cmsis-dap/src/cortex_m.rs: -------------------------------------------------------------------------------- 1 | //! Cortex-M specific operations 2 | 3 | use anyhow::bail; 4 | use cm::{ 5 | dcb::{dcrsr, demcr, dhcsr, DCRDR, DCRSR, DEMCR, DHCSR}, 6 | scb::{aircr, AIRCR}, 7 | }; 8 | use log::info; 9 | 10 | use crate::{adiv5, util}; 11 | 12 | /// Cortex-M register that the DAP can read 13 | #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 14 | #[repr(u8)] 15 | pub enum Register { 16 | /// R0 17 | R0 = 0b00000, 18 | 19 | /// R1 20 | R1 = 0b00001, 21 | 22 | /// R2 23 | R2 = 0b00010, 24 | 25 | /// R3 26 | R3 = 0b00011, 27 | 28 | /// R4 29 | R4 = 0b00100, 30 | 31 | /// R5 32 | R5 = 0b00101, 33 | 34 | /// R6 35 | R6 = 0b00110, 36 | 37 | /// R7 38 | R7 = 0b00111, 39 | 40 | /// R8 41 | R8 = 0b01000, 42 | 43 | /// R9 44 | R9 = 0b01001, 45 | 46 | /// R10 47 | R10 = 0b01010, 48 | 49 | /// R11 50 | R11 = 0b01011, 51 | 52 | /// R12 53 | R12 = 0b01100, 54 | 55 | /// Stack Pointer 56 | SP = 0b01101, 57 | 58 | /// Link register 59 | LR = 0b01110, 60 | 61 | /// Program Counter (AKA Debug Return Address) 62 | PC = 0b01111, 63 | 64 | /// Program Status Register 65 | XPSR = 0b10000, 66 | 67 | /// CONTROL - FAULTMASK - BASEPRI - PRIMASK 68 | CFBP = 0b10100, 69 | } 70 | 71 | impl Register { 72 | fn regsel(self) -> u8 { 73 | self as u8 74 | } 75 | } 76 | 77 | /// Debug Key 78 | const DBGKEY: u16 = 0xA05F; 79 | 80 | const RETRIES: usize = 2; 81 | 82 | impl crate::Dap { 83 | /// [ARM Cortex-M] Halts a Cortex-M target 84 | pub fn halt(&mut self) -> Result<(), anyhow::Error> { 85 | info!("halting the target ..."); 86 | 87 | self.set_debugen()?; 88 | 89 | let addr = DHCSR::address() as usize as u32; 90 | let dhcsr = dhcsr::R::from(self.memory_read_word(addr)?); 91 | if dhcsr.C_HALT() == 0 { 92 | let mut w = dhcsr::W::from(dhcsr); 93 | w.DBGKEY(DBGKEY).C_HALT(1).C_DEBUGEN(1); 94 | self.memory_write_word(addr, w.into())?; 95 | 96 | let mut dhcsr = 0; 97 | let c_halt = || { 98 | dhcsr = self.memory_read_word(addr)?; 99 | Ok(dhcsr::R::from(dhcsr).C_HALT() != 0) 100 | }; 101 | if !util::check(RETRIES, c_halt)? { 102 | bail!("failed to halt the target (DHCSR = {:#010x})", dhcsr); 103 | } 104 | } 105 | 106 | info!("... target halted"); 107 | 108 | Ok(()) 109 | } 110 | 111 | /// [ARM Cortex-M] Resumes ARM Cortex-M execution 112 | pub fn resume(&mut self) -> Result<(), anyhow::Error> { 113 | if !self.is_halted()? { 114 | info!("target is not halted"); 115 | return Ok(()); 116 | } 117 | 118 | info!("resuming target ..."); 119 | 120 | let addr = DHCSR::address() as usize as u32; 121 | let mut w = dhcsr::W::from(dhcsr::R::from(self.memory_read_word(addr)?)); 122 | w.DBGKEY(DBGKEY).C_HALT(0).C_DEBUGEN(1); 123 | self.memory_write_word(addr, w.into())?; 124 | 125 | info!("... target resumed"); 126 | 127 | Ok(()) 128 | } 129 | 130 | /// [ARM Cortex-M] Checks if the target is currently halted 131 | pub fn is_halted(&mut self) -> Result { 132 | Ok(if self.debugen == Some(true) { 133 | dhcsr::R::from(self.memory_read_word(DHCSR::address() as usize as u32)?).S_HALT() != 0 134 | } else { 135 | false 136 | }) 137 | } 138 | 139 | /// [ARM Cortex-M] Reads the specified target's core register 140 | pub fn read_core_register(&mut self, reg: Register) -> Result { 141 | const READ: u8 = 0; 142 | 143 | info!("reading Cortex-M register {:?} ... ", reg); 144 | 145 | let mut w = dcrsr::W::zero(); 146 | w.REGWnR(READ).REGSEL(reg.regsel()); 147 | self.memory_write_word(DCRSR::address() as usize as u32, w.into())?; 148 | 149 | let s_regrdy = || { 150 | Ok( 151 | dhcsr::R::from(self.memory_read_word(DHCSR::address() as usize as u32)?).S_REGRDY() 152 | != 0, 153 | ) 154 | }; 155 | if !util::check(RETRIES, s_regrdy)? { 156 | bail!("failed to read register {:?}", reg); 157 | } 158 | 159 | let word = self.memory_read_word(DCRDR::address() as usize as u32)?; 160 | 161 | info!("{:?} -> {:#010x}", reg, word); 162 | 163 | Ok(word) 164 | } 165 | 166 | /// [ARM Cortex-M] Requests a system reset 167 | pub fn sysresetreq(&mut self, halt: bool) -> Result<(), anyhow::Error> { 168 | /// Vector Key 169 | const VECTKEY: u16 = 0x05fa; 170 | 171 | info!("resetting the target with SYSRESETREQ ..."); 172 | 173 | self.set_debugen()?; 174 | 175 | let addr = DEMCR::address() as usize as u32; 176 | let mut w = demcr::W::from(demcr::R::from(self.memory_read_word(addr)?)); 177 | w.VC_CORERESET(if halt { 1 } else { 0 }); 178 | self.memory_write_word(addr, w.into())?; 179 | 180 | let addr = AIRCR::address() as usize as u32; 181 | let mut w = aircr::W::from(aircr::R::from(self.memory_read_word(addr)?)); 182 | w.VECTKEY(VECTKEY).SYSRESETREQ(1); 183 | self.memory_write_word(addr, w.into())?; 184 | 185 | // reset causes AHB_AP information to be lost; invalidate the cached state 186 | self.ap_bank = None; 187 | self.banked_data_mode = false; 188 | self.tar = None; 189 | self.debugen = Some(true); 190 | 191 | // wait for system and debug power-up 192 | let mut stat = self.read_register(adiv5::Register::DP_STAT)?; 193 | while stat & adiv5::DP_STAT_CDBGPWRUPACK == 0 { 194 | self.brief_sleep(); 195 | stat = self.read_register(adiv5::Register::DP_STAT)?; 196 | } 197 | 198 | while stat & adiv5::DP_STAT_CSYSPWRUPACK == 0 { 199 | self.brief_sleep(); 200 | stat = self.read_register(adiv5::Register::DP_STAT)?; 201 | } 202 | 203 | info!("... target reset"); 204 | Ok(()) 205 | } 206 | 207 | /// [ARM Cortex-M] Enables halting debug (DHCSR.C_DEBUGEN <- 1) 208 | /// 209 | /// Modifying some Cortex-M registers requires halting debug to be enabled 210 | pub fn set_debugen(&mut self) -> Result<(), anyhow::Error> { 211 | if self.debugen == Some(true) { 212 | return Ok(()); 213 | } 214 | 215 | let addr = DHCSR::address() as usize as u32; 216 | let dhcsr = dhcsr::R::from(self.memory_read_word(addr)?); 217 | if dhcsr.C_DEBUGEN() == 0 { 218 | info!("enabling halting debug mode"); 219 | let mut w = dhcsr::W::from(dhcsr); 220 | w.DBGKEY(DBGKEY).C_DEBUGEN(1); 221 | self.memory_write_word(addr, w.into())?; 222 | 223 | let mut dhcsr = 0; 224 | let c_debugen = || { 225 | dhcsr = self.memory_read_word(addr)?; 226 | Ok(dhcsr::R::from(dhcsr).C_DEBUGEN() != 0) 227 | }; 228 | if !util::check(RETRIES, c_debugen)? { 229 | bail!( 230 | "failed to enable halting debug mode (DHCSR = {:#010x})", 231 | dhcsr 232 | ) 233 | } 234 | } 235 | 236 | self.debugen = Some(true); 237 | 238 | Ok(()) 239 | } 240 | 241 | /// [ARM Cortex-M] Writes `val` to the specified target's core `reg`-ister 242 | pub fn write_core_register(&mut self, reg: Register, val: u32) -> Result<(), anyhow::Error> { 243 | const WRITE: u8 = 1; 244 | 245 | if !self.is_halted()? { 246 | bail!("core must be halted before writing to a core register") 247 | } 248 | 249 | info!("writing Cortex-M register {:?} ... ", reg); 250 | 251 | self.memory_write_word(DCRDR::address() as u32, val)?; 252 | 253 | let mut w = dcrsr::W::zero(); 254 | w.REGWnR(WRITE).REGSEL(reg.regsel()); 255 | self.memory_write_word(DCRSR::address() as u32, w.into())?; 256 | 257 | let addr = DHCSR::address() as u32; 258 | let s_regrdy = || Ok(dhcsr::R::from(self.memory_read_word(addr)?).S_REGRDY() != 0); 259 | if !util::check(RETRIES, s_regrdy)? { 260 | bail!("failed to write register {:?}", reg); 261 | } 262 | 263 | info!("{:?} <- {:#010x}", reg, val); 264 | 265 | Ok(()) 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /host/cmsis-dap/src/hid.rs: -------------------------------------------------------------------------------- 1 | //! USB-HID operations 2 | 3 | use std::time::Instant; 4 | 5 | use log::trace; 6 | 7 | pub(crate) const REPORT_ID: u8 = 0x00; 8 | 9 | impl crate::Dap { 10 | /// Pushes the data into the HID buffer 11 | pub(crate) fn hid_push(&mut self, data: impl AsLeBytes) { 12 | data.as_le_bytes(|bytes| { 13 | let n = bytes.len(); 14 | let cursor = usize::from(self.cursor); 15 | self.buffer[cursor..cursor + n].copy_from_slice(bytes); 16 | self.cursor += n as u16; 17 | }); 18 | } 19 | 20 | /// Rewrites a byte in the HID buffer 21 | pub(crate) fn hid_rewrite(&mut self, i: u16, val: u8) { 22 | assert!( 23 | i < self.cursor, 24 | "attempt to modify unused part of the HID buffer" 25 | ); 26 | self.buffer[usize::from(i)] = val; 27 | } 28 | 29 | /// Writes the contents of the HID buffer to the HID device 30 | pub(crate) fn hid_flush(&mut self) -> Result<(), anyhow::Error> { 31 | debug_assert_eq!(self.buffer[0], REPORT_ID, "first byte must be `REPORT_ID`"); 32 | 33 | let bytes = &self.buffer[..self.cursor.into()]; 34 | let start = Instant::now(); 35 | self.device.write(bytes)?; 36 | let end = Instant::now(); 37 | trace!("HID <- <{} bytes> in {:?}", bytes.len(), end - start); 38 | self.cursor = 1; 39 | 40 | Ok(()) 41 | } 42 | 43 | /// Reads `len` bytes from the HID device 44 | /// 45 | /// # Panics 46 | /// 47 | /// This function panics if 48 | /// 49 | /// - `hid_push` has been used but the HID buffer has not been drained 50 | /// - `len` exceeds the packet size supported by the target 51 | pub(crate) fn hid_read(&mut self, len: u16) -> Result<&[u8], anyhow::Error> { 52 | assert_eq!(self.cursor, 1, "HID buffer must be flushed before a read"); 53 | assert!( 54 | len <= self.packet_size, 55 | "requested HID exceeds the target's packet size" 56 | ); 57 | 58 | let buf = &mut self.buffer[1..]; 59 | let start = Instant::now(); 60 | let n = self.device.read(&mut buf[..usize::from(len)])?; 61 | assert_eq!(n, usize::from(len), "read less bytes than requested"); 62 | let bytes = &buf[..n]; 63 | let end = Instant::now(); 64 | trace!("HID -> <{} bytes> in {:?}", bytes.len(), end - start); 65 | Ok(bytes) 66 | } 67 | } 68 | 69 | pub(crate) trait AsLeBytes { 70 | fn as_le_bytes(&self, f: impl FnOnce(&[u8])); 71 | } 72 | 73 | impl AsLeBytes for &'_ T 74 | where 75 | T: AsLeBytes + ?Sized, 76 | { 77 | fn as_le_bytes(&self, f: impl FnOnce(&[u8])) { 78 | T::as_le_bytes(self, f) 79 | } 80 | } 81 | 82 | impl AsLeBytes for [u8] { 83 | fn as_le_bytes(&self, f: impl FnOnce(&[u8])) { 84 | f(self) 85 | } 86 | } 87 | 88 | impl AsLeBytes for u8 { 89 | fn as_le_bytes(&self, f: impl FnOnce(&[u8])) { 90 | f(&[*self]) 91 | } 92 | } 93 | 94 | impl AsLeBytes for u16 { 95 | fn as_le_bytes(&self, f: impl FnOnce(&[u8])) { 96 | f(&self.to_le_bytes()) 97 | } 98 | } 99 | 100 | impl AsLeBytes for u32 { 101 | fn as_le_bytes(&self, f: impl FnOnce(&[u8])) { 102 | f(&self.to_le_bytes()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /host/cmsis-dap/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! API to access a DAP device using the HID interface 2 | //! 3 | //! # References 4 | //! 5 | //! - 'ADIv5': ARM Debug Interface Architecture Specification ADIv5.0 to ADIv5.2 (ARM IHI 0031C) 6 | //! - 'ARMV7-M ARM': ARMv7-M Architecture Reference Manual (ARM DDI 0403E.d) 7 | //! - 'CoreSight': CoreSight Components Technical Reference Manual (ARM DDI 0314H) 8 | //! - 'Cortex-M4 TRM': Cortex-M4 Technical Reference Manual (ARM DDI 0439B) 9 | //! - [CMSIS-DAP 2.0](https://arm-software.github.io/CMSIS_5/DAP/html/index.html) 10 | 11 | #![deny(warnings)] 12 | 13 | use core::time::Duration; 14 | use std::thread; 15 | 16 | use anyhow::bail; 17 | use hidapi::{HidApi, HidDevice}; 18 | use log::{debug, info}; 19 | 20 | // comment indicates the abstraction level (0: lowest, 9: highest) 21 | pub mod adiv5; // 2 22 | mod ahb_ap; // 3 23 | pub mod cortex_m; // 4 24 | pub mod dap; // 1 25 | mod hid; // 0 26 | mod sealed; 27 | mod util; 28 | 29 | /// A CMSIS-DAP Debug Unit 30 | pub struct Dap { 31 | device: HidDevice, 32 | buffer: Box<[u8]>, 33 | 34 | // property of the target 35 | packet_size: u16, 36 | caps_atomic: Option, 37 | 38 | // transfer buffer 39 | total_requests: u8, 40 | read_requests: u8, 41 | cursor: u16, 42 | ap_bank: Option, 43 | banked_data_mode: bool, 44 | 45 | // AHB-AP specific 46 | tar: Option, 47 | 48 | // Cortex-M specific 49 | debugen: Option, 50 | } 51 | 52 | /* # Utility functions */ 53 | // XXX are these reasonable defaults? 54 | const DEFAULT_SWD_FREQUENCY: u32 = 4_000_000; 55 | const DEFAULT_WAIT_RETRY: u16 = 1; 56 | 57 | impl crate::Dap { 58 | /// Opens the DAP Debug Unit that matches the given vendor and product IDs 59 | pub fn open(vendor: u16, product: u16, sn: Option<&str>) -> Result { 60 | let hid = HidApi::new()?; 61 | let device = if let Some(sn) = sn { 62 | hid.open_serial(vendor, product, sn)? 63 | } else { 64 | hid.open(vendor, product)? 65 | }; 66 | 67 | let mut dap = Self { 68 | buffer: Box::new([crate::hid::REPORT_ID; 5]), 69 | device, 70 | 71 | caps_atomic: None, 72 | ap_bank: None, 73 | banked_data_mode: false, 74 | cursor: 1, 75 | debugen: None, 76 | read_requests: 0, 77 | tar: None, 78 | total_requests: 0, 79 | 80 | // initial conservative assumption 81 | packet_size: 4, 82 | }; 83 | 84 | // grow the buffer to match the target's supported packet size 85 | dap.packet_size = dap.packet_size()?; 86 | dap.buffer = Box::<[u8]>::from(vec![0; usize::from(dap.packet_size)]); 87 | 88 | Ok(dap) 89 | } 90 | 91 | /// Returns the USB serial number 92 | pub fn serial_number(&self) -> Option { 93 | self.device.get_serial_number_string().unwrap_or(None) 94 | } 95 | 96 | /// Configures the Debug Unit to use the SWD interface, puts the target in SWD mode and powers 97 | /// up the target's debug domain 98 | pub fn default_swd_configuration(&mut self) -> Result<(), anyhow::Error> { 99 | info!("confirming SWD support"); 100 | let caps = self.capabilities()?; 101 | if caps & dap::CAPABILITIES_SWD == 0 { 102 | bail!("DAP does not support SWD") 103 | } 104 | self.caps_atomic = Some(caps & dap::CAPABILITIES_ATOMIC != 0); 105 | 106 | info!("initializing SWD interface"); 107 | self.connect(dap::Mode::SWD)?; 108 | 109 | info!( 110 | "setting SWD clock frequency to {} MHz", 111 | DEFAULT_SWD_FREQUENCY / 1_000_000 112 | ); 113 | self.swj_clock(DEFAULT_SWD_FREQUENCY)?; 114 | 115 | info!("configuring transfer to retry on WAIT responses from the target"); 116 | self.transfer_configure(0, DEFAULT_WAIT_RETRY, 0)?; 117 | 118 | info!("switching the target's connection mode from JTAG to SWD"); 119 | self.swj_sequence(dap::JTAG_TO_SWD_SWJ_SEQUENCE)?; 120 | 121 | // XXX for some reason debug power-up fails without first reading DPIDR 122 | let dpidr = self.read_register(adiv5::Register::DP_DPIDR)?; 123 | if (dpidr & ((1 << 12) - 1)) 124 | != (adiv5::DP_DPIDR_RESERVED | adiv5::DP_DPIDR_MANUFACTURER_ARM) 125 | { 126 | bail!( 127 | "target device doesn't appear to be an ARM device (DPIDR = {:#010x})", 128 | dpidr 129 | ); 130 | } 131 | let version = (dpidr >> 12) & ((1 << 3) - 1); 132 | info!( 133 | "Debug Port version: {} (DPIDR = {:#010x})", 134 | if version == 2 { 135 | "DPv2" 136 | } else if version == 1 { 137 | "DPv1" 138 | } else { 139 | bail!("only DPv1 and DPv2 are supported"); 140 | }, 141 | dpidr 142 | ); 143 | 144 | // "Tools can only initiate an AP transfer when CDBGPWRUPREQ and 145 | // CDBGPWRUPACK are asserted HIGH. If CDBGPWRUPREQ or CDBGPWRUPACK is 146 | // LOW, any AP transfer generates an immediate fault response.", section 147 | // 2.4.2 of ADIv5 148 | let stat = self.read_register(adiv5::Register::DP_STAT)?; 149 | let stat = if stat & adiv5::DP_STAT_CDBGPWRUPACK == 0 { 150 | debug!("debug power-up request"); 151 | self.push_dap_transfer_request( 152 | adiv5::Register::DP_CTRL, 153 | dap::Request::Write( 154 | (stat & adiv5::DP_CTRL_CSYSPWRUPREQ) | adiv5::DP_CTRL_CDBGPWRUPREQ, 155 | ), 156 | ); 157 | self.push_dap_transfer_request(adiv5::Register::DP_STAT, dap::Request::Read); 158 | let stat = self.execute_dap_transfer()?[0]; 159 | if stat & adiv5::DP_STAT_CDBGPWRUPACK == 0 { 160 | bail!("debug power-up request failed"); 161 | } 162 | stat 163 | } else { 164 | stat 165 | }; 166 | 167 | if stat & adiv5::DP_STAT_CSYSPWRUPACK == 0 { 168 | debug!("system power-up request"); 169 | self.push_dap_transfer_request( 170 | adiv5::Register::DP_CTRL, 171 | dap::Request::Write( 172 | (stat & adiv5::DP_CTRL_CDBGPWRUPREQ) | adiv5::DP_CTRL_CSYSPWRUPREQ, 173 | ), 174 | ); 175 | self.push_dap_transfer_request(adiv5::Register::DP_STAT, dap::Request::Read); 176 | let stat = self.execute_dap_transfer()?[0]; 177 | if stat & adiv5::DP_STAT_CSYSPWRUPACK == 0 { 178 | bail!("system power-up request failed"); 179 | } 180 | } 181 | 182 | Ok(()) 183 | } 184 | 185 | /// Returns `true` if the probe supports atomic commands 186 | pub fn supports_atomic_commands(&mut self) -> Result { 187 | if let Some(atomic) = self.caps_atomic { 188 | Ok(atomic) 189 | } else { 190 | let caps_atomic = self.capabilities()? & dap::CAPABILITIES_ATOMIC != 0; 191 | self.caps_atomic = Some(caps_atomic); 192 | Ok(caps_atomic) 193 | } 194 | } 195 | 196 | /// Sleep for a bit to let the target make progress 197 | fn brief_sleep(&self) { 198 | thread::sleep(Duration::from_micros( 199 | 64 * 1_000_000 / u64::from(DEFAULT_SWD_FREQUENCY), 200 | )); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /host/cmsis-dap/src/sealed.rs: -------------------------------------------------------------------------------- 1 | /// Data that can be read from memory using the AHB-AP (Access Port) 2 | pub trait Data { 3 | const BYTES: u16; 4 | const CSW_SIZE: u32; 5 | 6 | fn acronym() -> &'static str; 7 | fn push(memory: &mut Vec, bytes: &[u8], offset: u32, count: u16) 8 | where 9 | Self: Sized; 10 | } 11 | -------------------------------------------------------------------------------- /host/cmsis-dap/src/util.rs: -------------------------------------------------------------------------------- 1 | pub fn round_down(x: u16, n: u16) -> u16 { 2 | let rem = x % n; 3 | if rem != 0 { 4 | x - rem 5 | } else { 6 | x 7 | } 8 | } 9 | 10 | pub fn round_up(x: u16, n: u16) -> u16 { 11 | let rem = x % n; 12 | if rem != 0 { 13 | n + (x - rem) 14 | } else { 15 | x 16 | } 17 | } 18 | 19 | pub fn check( 20 | retries: usize, 21 | mut cond: impl FnMut() -> Result, 22 | ) -> Result { 23 | for _ in 0..retries { 24 | if cond()? { 25 | return Ok(true); 26 | } 27 | } 28 | 29 | Ok(false) 30 | } 31 | -------------------------------------------------------------------------------- /host/executor-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "executor-macros" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | proc-macro2 = "1.0.9" 14 | syn = "1.0.17" 15 | quote = "1.0.3" 16 | proc-macro-hack = "0.5.12" 17 | -------------------------------------------------------------------------------- /host/executor-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | extern crate proc_macro; 4 | 5 | use proc_macro::TokenStream; 6 | 7 | use proc_macro2::Span as Span2; 8 | use proc_macro_hack::proc_macro_hack; 9 | use quote::{format_ident, quote}; 10 | use syn::{ 11 | parse::{self, Parse, ParseBuffer}, 12 | parse_macro_input, 13 | punctuated::Punctuated, 14 | Ident, Token, 15 | }; 16 | 17 | #[proc_macro_hack] 18 | pub fn run(input: TokenStream) -> TokenStream { 19 | let idents = parse_macro_input!(input as Input).idents; 20 | let ntasks = idents.len(); 21 | 22 | if ntasks == 0 { 23 | return parse::Error::new(Span2::call_site(), "expected at least one task") 24 | .to_compile_error() 25 | .into(); 26 | } 27 | 28 | let mut stmts = vec![]; 29 | let mut polls = vec![]; 30 | 31 | let krate = format_ident!("executor"); 32 | 33 | // check that idents are futures and pin them 34 | for ident in idents.iter() { 35 | stmts.push(quote!( 36 | let mut #ident = #krate::check(#ident); 37 | // the future will never be moved 38 | let mut #ident = unsafe { core::pin::Pin::new_unchecked(&mut #ident) }; 39 | )); 40 | 41 | polls.push(quote!( 42 | // XXX do we want to prevent futures being polled beyond completion? 43 | let _ = #ident.as_mut().poll(&mut cx); 44 | )); 45 | } 46 | 47 | stmts.push(quote!( 48 | let waker = #krate::waker(); 49 | let mut cx = core::task::Context::from_waker(&waker); 50 | 51 | loop { 52 | use core::future::Future as _; 53 | 54 | #(#polls)* 55 | #krate::wfe(); 56 | } 57 | )); 58 | 59 | quote!({ 60 | #(#stmts)* 61 | }) 62 | .into() 63 | } 64 | 65 | struct Input { 66 | idents: Punctuated, 67 | } 68 | 69 | impl Parse for Input { 70 | fn parse(input: &ParseBuffer) -> parse::Result { 71 | Ok(Self { 72 | idents: Punctuated::parse_separated_nonempty(input)?, 73 | }) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /host/hidc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "hidc" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | anyhow = "1.0.27" 11 | consts = { path = "../../shared/consts" } 12 | hidapi = "1.2.2" 13 | -------------------------------------------------------------------------------- /host/hidc/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use anyhow::{anyhow, bail, ensure}; 4 | use hidapi::HidApi; 5 | 6 | fn main() -> Result<(), anyhow::Error> { 7 | let args = env::args() 8 | .skip(1) // skip program name 9 | .collect::>(); 10 | ensure!(!args.is_empty(), "expected exactly one argument"); 11 | 12 | let api = HidApi::new()?; 13 | let dev = api 14 | .device_list() 15 | .filter(|dev| dev.vendor_id() == consts::VID && dev.product_id() == consts::PID) 16 | .next() 17 | .ok_or_else(|| anyhow!("device not found"))? 18 | .open_device(&api)?; 19 | 20 | let chan = args[0].parse::()?; 21 | if chan < 11 || chan > 26 { 22 | bail!("channel is out of range (`11..=26`)") 23 | } 24 | dev.write(&[0, chan])?; 25 | println!("requested channel change to channel {}", chan); 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /host/regen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "regen" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | anyhow = "1.0.26" 11 | heck = "0.3.1" 12 | proc-macro2 = "1.0.8" 13 | quote = "1.0.2" 14 | svd-parser = "0.9.0" 15 | syn = "1.0.14" 16 | rand_xorshift = "0.2.0" 17 | rand_core = "0.5.1" 18 | rand = "0.7.3" 19 | -------------------------------------------------------------------------------- /host/regen/README.md: -------------------------------------------------------------------------------- 1 | # `Regen` 🌧️ 2 | 3 | > (Yet another) Register API Generator 4 | 5 | ## Highlights 6 | 7 | ### Pay (in compile time) for want you use 8 | 9 | Why compile the API for dozens of peripherals when your application will only 10 | use half a dozen? Until the compiler becomes smart enough to figure this out by 11 | itself `regen` will put peripherals behind opt-in Cargo feature. If you need a 12 | peripheral enable its Cargo feature. All the instances of a peripheral (e.g. 13 | `UART1`, `UART2`, etc.) will be gated behind a single Cargo feature. 14 | 15 | ### `!dereferenceable` 16 | 17 | `regen` sidesteps issue [rust-lang/rust#55005][deferenceable] by never creating 18 | a reference (`&[mut]-`) to MMIO registers. It's raw pointers all the way down. 19 | 20 | [deferenceable]: https://github.com/rust-lang/rust/issues/55005 21 | 22 | ### One IR; many data formats 23 | 24 | `regen` features an Intermediate Representation (IR) format that's designed for 25 | optimization (merging individual, but equivalent, registers and / or bitfields 26 | into clusters / arrays) and code generation. The IR has no defined 27 | serialization format. Instead third parties can write translation libraries that 28 | parse XML, C header or PDF files and translate the register data into `regen`'s 29 | IR. Then `regen`'s public API can be used to optimize the IR and lower it to 30 | Rust code. 31 | 32 | ### Minimal `unsafe` API 33 | 34 | By default, `regen` considers reading and writing to any register to be safe. 35 | Sometimes writes can produce potentially memory unsafe side effects like 36 | unmasking an exception / interrupt (which can break critical sections), changing 37 | the priority of an exception / interrupt (which can also break a critical 38 | section), starting a DMA transfer (which can overwrite not owned memory). 39 | 40 | `regen`'s IR includes a field to make register writes `unsafe`. As most data 41 | sources do not encode potential memory unsafety, this information must be 42 | provide by the person generating the crate; that is they must audit the safety 43 | of all register writes. One more reason to not blindly generate code for all the 44 | peripherals in your device! 45 | 46 | ### Singletons are always singletons 47 | 48 | Peripheral are represented as *owned* (not global) singletons. The `regen` API 49 | does *not* provide a method, not even a `unsafe` one, to create *more* than one 50 | instance of any peripheral singleton as more than one instance of a singleton 51 | ever existing would be semantically unsound. Singleton instances are created 52 | using the `take` method; this method returns the singleton instance only once. 53 | 54 | ### Register granularity 55 | 56 | The registers (fields) within a peripheral (`struct`) can be moved into 57 | abstractions. This lets you finely control which registers an abstraction has 58 | access to. Registers are also singletons so `drop`-ing them means they'll never 59 | be modified in software again; this can be used to seal the configuration of a 60 | peripheral. For example, if you set the baud rate of `UART1` to 115,200 and then 61 | drop the configuration register, the application will not be able to change 62 | the baud rate after than point. 63 | -------------------------------------------------------------------------------- /host/regen/src/codegen/util.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; 2 | use quote::{format_ident, quote}; 3 | use syn::{Ident, LitInt}; 4 | 5 | use crate::{ 6 | fmt::Hex, 7 | ir::{Bitfield, Register, Width}, 8 | }; 9 | 10 | pub fn ident(s: &str) -> Ident { 11 | format_ident!( 12 | "{}", 13 | match s { 14 | // keywords 15 | "in" => "in_", 16 | _ => s, 17 | } 18 | ) 19 | } 20 | 21 | pub fn bitwidth2ty(width: u8) -> TokenStream2 { 22 | if width <= 8 { 23 | quote!(u8) 24 | } else if width <= 16 { 25 | quote!(u16) 26 | } else if width <= 32 { 27 | quote!(u32) 28 | } else if width <= 64 { 29 | quote!(u64) 30 | } else { 31 | unreachable!() 32 | } 33 | } 34 | 35 | pub fn hex(val: u64) -> LitInt { 36 | LitInt::new(&Hex(val).to_string(), Span2::call_site()) 37 | } 38 | 39 | pub fn r2wmask(reg: &Register<'_>) -> u64 { 40 | let mut mask = 0; 41 | for field in ®.r_fields { 42 | if !reg.w_fields.contains(field) { 43 | mask |= field.mask() << field.offset; 44 | } 45 | } 46 | mask 47 | } 48 | 49 | pub fn unsuffixed(val: u8) -> LitInt { 50 | LitInt::new(&val.to_string(), Span2::call_site()) 51 | } 52 | 53 | pub fn width2ty(width: Width) -> TokenStream2 { 54 | match width { 55 | Width::U8 => quote!(u8), 56 | Width::U16 => quote!(u16), 57 | Width::U32 => quote!(u32), 58 | Width::U64 => quote!(u64), 59 | } 60 | } 61 | 62 | pub fn field_docs(field: &Bitfield<'_>) -> String { 63 | let mut doc = if field.width == 1 { 64 | format!("(Bit {})", field.offset) 65 | } else { 66 | format!("(Bits {}..={})", field.offset, field.offset + field.width) 67 | }; 68 | if let Some(desc) = field.description.as_ref() { 69 | doc.push(' '); 70 | doc.push_str(desc); 71 | } 72 | doc 73 | } 74 | -------------------------------------------------------------------------------- /host/regen/src/fmt.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | pub struct Hex(pub T); 4 | 5 | impl fmt::Display for Hex { 6 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 7 | write!(f, "{:#04x}", self.0) 8 | } 9 | } 10 | 11 | impl fmt::Display for Hex { 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | if self.0 <= 0xff { 14 | Hex(self.0 as u8).fmt(f) 15 | } else if self.0 <= 0xffff { 16 | write!(f, "{:#06x}", self.0) 17 | } else if self.0 <= 0xffff_ffff { 18 | write!(f, "{:#06x}_{:04x}", self.0 >> 16, self.0 as u16) 19 | } else { 20 | unimplemented!() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /host/regen/src/ir.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | pub struct Device<'a> { 4 | pub extra_docs: Option>, 5 | pub name: Cow<'a, str>, 6 | pub peripherals: Vec>, 7 | } 8 | 9 | pub enum Instances<'a> { 10 | Single { base_address: u64 }, 11 | Many { instances: Vec> }, 12 | } 13 | 14 | pub struct Instance<'a> { 15 | pub suffix: Cow<'a, str>, 16 | pub base_address: u64, 17 | } 18 | 19 | pub struct Peripheral<'a> { 20 | pub description: Option>, 21 | pub instances: Instances<'a>, 22 | pub name: Cow<'a, str>, 23 | pub registers: Vec>, 24 | } 25 | 26 | pub struct Register<'a> { 27 | pub access: Access, 28 | pub description: Option>, 29 | pub name: Cow<'a, str>, 30 | pub offset: u64, 31 | pub r_fields: Vec>, 32 | pub w_fields: Vec>, 33 | /// In *bytes*; must be one of `[1, 2, 4, 8]` 34 | pub width: Width, 35 | } 36 | 37 | /// Register width 38 | #[derive(Clone, Copy)] 39 | pub enum Width { 40 | U8, 41 | U16, 42 | U32, 43 | U64, 44 | } 45 | 46 | impl Width { 47 | pub fn bits(self) -> u8 { 48 | match self { 49 | Width::U8 => 8, 50 | Width::U16 => 16, 51 | Width::U32 => 32, 52 | Width::U64 => 64, 53 | } 54 | } 55 | } 56 | 57 | /// Register access 58 | #[derive(Clone, Copy, PartialEq)] 59 | pub enum Access { 60 | ReadOnly, 61 | WriteOnly { unsafe_write: bool }, 62 | ReadWrite { unsafe_write: bool }, 63 | } 64 | 65 | impl Access { 66 | pub fn can_read(self) -> bool { 67 | match self { 68 | Access::ReadOnly | Access::ReadWrite { .. } => true, 69 | Access::WriteOnly { .. } => false, 70 | } 71 | } 72 | 73 | pub fn can_write(self) -> bool { 74 | match self { 75 | Access::WriteOnly { .. } | Access::ReadWrite { .. } => true, 76 | Access::ReadOnly => false, 77 | } 78 | } 79 | 80 | pub fn write_is_unsafe(&self) -> bool { 81 | match self { 82 | Access::WriteOnly { unsafe_write } | Access::ReadWrite { unsafe_write } => { 83 | *unsafe_write 84 | } 85 | _ => false, 86 | } 87 | } 88 | 89 | pub fn make_write_unsafe(&mut self) { 90 | match self { 91 | Access::WriteOnly { unsafe_write } | Access::ReadWrite { unsafe_write } => { 92 | *unsafe_write = true 93 | } 94 | _ => panic!("`make_write_unsafe` called on a register with no write access"), 95 | } 96 | } 97 | } 98 | 99 | #[derive(Clone, Eq, PartialEq)] 100 | pub struct Bitfield<'a> { 101 | pub description: Option>, 102 | pub name: Cow<'a, str>, 103 | /// In bits; must be less than the register width 104 | pub offset: u8, 105 | /// In bits; must be greater than `0` and less than the register width 106 | pub width: u8, 107 | } 108 | 109 | impl Bitfield<'_> { 110 | pub fn mask(&self) -> u64 { 111 | (1 << self.width) - 1 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /host/regen/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(rust_2018_compatibility)] 2 | #![deny(rust_2018_idioms)] 3 | #![deny(warnings)] 4 | 5 | // TODO split most modules into library 6 | 7 | pub mod cm; 8 | pub mod codegen; 9 | mod fmt; 10 | pub mod ir; 11 | pub mod opt; 12 | mod translate; 13 | mod verify; 14 | 15 | use std::{fs, path::Path}; 16 | 17 | fn main() -> Result<(), anyhow::Error> { 18 | gen_cm(Path::new("../../shared/cm/src/lib.rs"))?; 19 | gen_nrf52(Path::new("../../firmware/pac/src/lib.rs"))?; 20 | 21 | Ok(()) 22 | } 23 | 24 | // Audited register writes 25 | const AUDITED: &[&str] = &[ 26 | "CLOCK", "FICR", "P0", "POWER", "RADIO", "RTC0", "TWIM0", "USBD", "SPIM0", 27 | ]; 28 | 29 | fn gen_nrf52(lib: &Path) -> Result<(), anyhow::Error> { 30 | let xml = fs::read_to_string("nrf52840.svd")?; 31 | let dev = svd_parser::parse(&xml)?; 32 | let mut dev = translate::svd::device(&dev, AUDITED); 33 | audit_nrf52(&mut dev); 34 | gen(dev, lib) 35 | } 36 | 37 | fn audit_nrf52(dev: &mut ir::Device<'_>) { 38 | for periph in &mut dev.peripherals { 39 | // all peripherals 40 | for reg in &mut periph.registers { 41 | match &*reg.name { 42 | // enabling interrupts can break critical sections 43 | "INTEN" | "INTENSET" => { 44 | reg.access.make_write_unsafe(); 45 | } 46 | _ => {} 47 | } 48 | } 49 | 50 | // Fix bitfield widths to match the OPS 51 | if periph.name == "TWIM0" { 52 | for reg in &mut periph.registers { 53 | match &*reg.name { 54 | "RXD_AMOUNT" | "TXD_AMOUNT" => { 55 | for field in reg.r_fields.iter_mut().chain(&mut reg.w_fields) { 56 | if field.name == "AMOUNT" { 57 | field.width = 8; 58 | } 59 | } 60 | } 61 | 62 | "RXD_MAXCNT" | "TXD_MAXCNT" => { 63 | // DMA related 64 | reg.access.make_write_unsafe(); 65 | 66 | for field in reg.r_fields.iter_mut().chain(&mut reg.w_fields) { 67 | if field.name == "MAXCNT" { 68 | field.width = 8; 69 | } 70 | } 71 | } 72 | 73 | // DMA related 74 | "TASKS_STARTRX" | "TASKS_STARTTX" | "RXD_PTR" | "TXD_PTR" => { 75 | reg.access.make_write_unsafe(); 76 | } 77 | 78 | _ => {} 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | fn gen_cm(lib: &Path) -> Result<(), anyhow::Error> { 86 | let dev = cm::device(); 87 | gen(dev, lib) 88 | } 89 | 90 | fn gen(mut dev: ir::Device<'_>, lib: &Path) -> Result<(), anyhow::Error> { 91 | assert!(lib.is_file()); 92 | 93 | dev.verify()?; 94 | opt::device(&mut dev); 95 | let krate = codegen::device(&dev); 96 | fs::write(lib, krate)?; 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /host/regen/src/opt.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::Device; 2 | 3 | pub fn device(_device: &mut Device<'_>) {} 4 | -------------------------------------------------------------------------------- /host/regen/src/translate.rs: -------------------------------------------------------------------------------- 1 | pub mod svd; 2 | -------------------------------------------------------------------------------- /host/regen/src/translate/svd.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryInto; 2 | 3 | use svd_parser as svd; 4 | 5 | use crate::{ir, translate::svd as translate}; 6 | 7 | pub fn device<'a>(d: &'a svd::Device, whitelist: &[&str]) -> ir::Device<'a> { 8 | let defaults = &d.default_register_properties; 9 | let mut peripherals = vec![]; 10 | for periph in &d.peripherals { 11 | if whitelist.contains(&&*periph.name) { 12 | // skip peripheral with no registers 13 | if let Some(regs) = periph.registers.as_ref() { 14 | peripherals.push(translate::peripheral(&periph, regs, defaults)); 15 | } 16 | } 17 | } 18 | 19 | ir::Device { 20 | extra_docs: None, 21 | name: d.name.as_str().into(), 22 | peripherals, 23 | } 24 | } 25 | 26 | pub fn peripheral<'a>( 27 | p: &'a svd::Peripheral, 28 | regs: &'a [svd::RegisterCluster], 29 | defaults: &svd::RegisterProperties, 30 | ) -> ir::Peripheral<'a> { 31 | assert!(p.derived_from.is_none()); 32 | 33 | let mut ir_regs = vec![]; 34 | 35 | for cluster in regs { 36 | match cluster { 37 | svd::RegisterCluster::Register(r) => register_(r, None, &[defaults], &mut ir_regs), 38 | 39 | svd::RegisterCluster::Cluster(cluster) => match cluster { 40 | svd::Cluster::Single(info) => { 41 | for child in &info.children { 42 | match child { 43 | svd::RegisterCluster::Register(r) => register_( 44 | r, 45 | Some(info), 46 | &[&info.default_register_properties, defaults], 47 | &mut ir_regs, 48 | ), 49 | 50 | svd::RegisterCluster::Cluster(..) => unimplemented!(), 51 | } 52 | } 53 | } 54 | 55 | svd::Cluster::Array(ci, dim) => { 56 | assert!(dim.dim_index.is_none(), "unimplemented"); 57 | assert!(ci.name.contains("[%s]"), "unimplemented"); 58 | 59 | let template = &ci.name; 60 | let offset = ci.address_offset; 61 | 62 | for i in 0..dim.dim { 63 | // FIXME too lazy to do ownership correctly right now 64 | let ci: &'static mut _ = Box::leak(Box::new(ci.clone())); 65 | 66 | ci.name = template.replace("[%s]", &i.to_string()); 67 | ci.address_offset = offset + i * dim.dim_increment; 68 | 69 | for child in &ci.children { 70 | match child { 71 | svd::RegisterCluster::Register(ri) => { 72 | ir_regs.push(translate::register( 73 | ri, 74 | Some(ci), 75 | &[&ci.default_register_properties, defaults], 76 | )); 77 | } 78 | 79 | svd::RegisterCluster::Cluster(..) => unimplemented!(), 80 | } 81 | } 82 | } 83 | } 84 | }, 85 | } 86 | } 87 | 88 | ir::Peripheral { 89 | name: p.name.as_str().into(), 90 | description: p.description.as_ref().map(|s| s.into()), 91 | instances: ir::Instances::Single { 92 | base_address: u64::from(p.base_address), 93 | }, 94 | registers: ir_regs, 95 | } 96 | } 97 | 98 | fn register_<'a>( 99 | r: &'a svd::Register, 100 | ci: Option<&svd::ClusterInfo>, 101 | defaults: &[&svd::RegisterProperties], 102 | ir_regs: &mut Vec>, 103 | ) { 104 | match r { 105 | svd::Register::Single(ri) => { 106 | ir_regs.push(translate::register(ri, ci, defaults)); 107 | } 108 | 109 | svd::Register::Array(ri, dim) => { 110 | assert!(dim.dim_index.is_none(), "unimplemented"); 111 | assert!(ri.name.contains("[%s]"), "unimplemented"); 112 | 113 | let template = &ri.name; 114 | let offset = ri.address_offset; 115 | 116 | for i in 0..dim.dim { 117 | // FIXME too lazy to do ownership correctly right now 118 | let mut ri: &'static mut _ = Box::leak(Box::new(ri.clone())); 119 | 120 | ri.name = template.replace("[%s]", &i.to_string()); 121 | ri.address_offset = offset + i * dim.dim_increment; 122 | 123 | ir_regs.push(translate::register(ri, ci, defaults)); 124 | } 125 | } 126 | } 127 | } 128 | 129 | pub fn register<'a>( 130 | r: &'a svd::RegisterInfo, 131 | cluster: Option<&svd::ClusterInfo>, 132 | defaults: &[&svd::RegisterProperties], 133 | ) -> ir::Register<'a> { 134 | let (r_fields, w_fields) = r 135 | .fields 136 | .as_ref() 137 | .map(|f| translate::fields(f, r)) 138 | .unwrap_or((vec![], vec![])); 139 | 140 | let (name, offset) = if let Some(cluster) = cluster { 141 | ( 142 | format!("{}_{}", cluster.name, r.name).into(), 143 | cluster.address_offset + r.address_offset, 144 | ) 145 | } else { 146 | (r.name.as_str().into(), r.address_offset) 147 | }; 148 | 149 | ir::Register { 150 | access: r 151 | .access 152 | .or_else(|| defaults.iter().filter_map(|default| default.access).next()) 153 | .map(translate::access) 154 | .expect("unimplemented"), 155 | description: r.description.as_ref().map(|s| s.as_str().into()), 156 | name, 157 | r_fields, 158 | w_fields, 159 | offset: u64::from(offset), 160 | width: r 161 | .size 162 | .or_else(|| defaults.iter().filter_map(|default| default.size).next()) 163 | .map(translate::register_size) 164 | .expect("unimplemented"), 165 | } 166 | } 167 | 168 | pub fn access(access: svd::Access) -> ir::Access { 169 | match access { 170 | svd::Access::ReadOnly => ir::Access::ReadOnly, 171 | svd::Access::WriteOnly => ir::Access::WriteOnly { 172 | unsafe_write: false, 173 | }, 174 | svd::Access::ReadWrite => ir::Access::ReadWrite { 175 | unsafe_write: false, 176 | }, 177 | _ => unimplemented!("{:?}", access), 178 | } 179 | } 180 | 181 | pub fn fields<'a>( 182 | fields: &'a [svd::Field], 183 | reg: &'a svd::RegisterInfo, 184 | ) -> (Vec>, Vec>) { 185 | let mut r_fields = vec![]; 186 | let mut w_fields = vec![]; 187 | 188 | for field in fields { 189 | match field { 190 | svd::Field::Single(fi) => { 191 | let (offset, width) = translate::bit_range(fi.bit_range); 192 | let bf = ir::Bitfield { 193 | description: fi.description.as_ref().map(|s| s.as_str().into()), 194 | name: fi.name.as_str().into(), 195 | offset, 196 | width, 197 | }; 198 | 199 | match fi.access.or(reg.access).expect("unreachable") { 200 | svd::Access::ReadOnly => r_fields.push(bf), 201 | svd::Access::WriteOnly => w_fields.push(bf), 202 | svd::Access::ReadWrite => { 203 | r_fields.push(bf.clone()); 204 | w_fields.push(bf); 205 | } 206 | access => unimplemented!("{:?}", access), 207 | } 208 | } 209 | svd::Field::Array(..) => unimplemented!(), 210 | } 211 | } 212 | 213 | (r_fields, w_fields) 214 | } 215 | 216 | fn register_size(size: u32) -> ir::Width { 217 | match size { 218 | 8 => ir::Width::U8, 219 | 16 => ir::Width::U16, 220 | 32 => ir::Width::U32, 221 | 64 => ir::Width::U64, 222 | _ => unreachable!(), 223 | } 224 | } 225 | 226 | fn bit_range(br: svd::BitRange) -> (u8, u8) { 227 | ( 228 | br.offset.try_into().expect("unreachable"), 229 | br.width.try_into().expect("unreachable"), 230 | ) 231 | } 232 | -------------------------------------------------------------------------------- /host/regen/src/verify.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context}; 2 | 3 | use crate::{ 4 | fmt::Hex, 5 | ir::{Bitfield, Device, Instances, Peripheral, Register}, 6 | }; 7 | 8 | impl Device<'_> { 9 | pub fn verify(&self) -> Result<(), anyhow::Error> { 10 | for peripheral in &self.peripherals { 11 | peripheral.verify()?; 12 | } 13 | 14 | Ok(()) 15 | } 16 | } 17 | 18 | impl Peripheral<'_> { 19 | fn verify(&self) -> Result<(), anyhow::Error> { 20 | if self.name.is_empty() { 21 | bail!("unnamed peripheral"); 22 | } 23 | 24 | (|| { 25 | if is_invalid_ident(&self.name) { 26 | bail!("name is not a valid identifier"); 27 | } 28 | 29 | match &self.instances { 30 | Instances::Many { instances } => { 31 | let n = instances.len(); 32 | if n < 2 { 33 | bail!( 34 | "specified `Instances::Many` but it contains less than 2 ({}) ", 35 | n 36 | ) 37 | } 38 | } 39 | 40 | Instances::Single { .. } => {} 41 | } 42 | 43 | for reg in &self.registers { 44 | reg.verify()?; 45 | 46 | // TODO check for register overlap 47 | } 48 | 49 | Ok(()) 50 | })() 51 | .context(format!("while verifying peripheral {}", self.name))?; 52 | 53 | Ok(()) 54 | } 55 | } 56 | 57 | impl Register<'_> { 58 | fn verify(&self) -> Result<(), anyhow::Error> { 59 | if self.name.is_empty() { 60 | bail!("unnamed register with offset {}", Hex(self.offset)); 61 | } 62 | 63 | (|| { 64 | if is_invalid_ident(&self.name) { 65 | bail!("name is not a valid identifier"); 66 | } 67 | 68 | let reg_width = self.width.bits(); 69 | for field in self.r_fields.iter().chain(&self.w_fields) { 70 | field.verify()?; 71 | 72 | if field.width + field.offset > reg_width { 73 | bail!( 74 | "bitfield {} (offset: {}, width: {}) exceeds register width ({})", 75 | field.name, 76 | field.offset, 77 | field.width, 78 | reg_width, 79 | ) 80 | } 81 | } 82 | 83 | fn check_for_overlap(fields: &[Bitfield<'_>]) -> Result<(), anyhow::Error> { 84 | let mut used: u64 = 0; 85 | for field in fields { 86 | let mask = ((1 << field.width) - 1) << field.offset; 87 | 88 | if used & mask != 0 { 89 | bail!("bitfield {} overlaps with other bitfields", field.name); 90 | } 91 | 92 | used |= mask; 93 | } 94 | 95 | Ok(()) 96 | } 97 | 98 | check_for_overlap(&self.r_fields)?; 99 | check_for_overlap(&self.w_fields)?; 100 | 101 | Ok(()) 102 | })() 103 | .context(format!("while verifying register {}", self.name))?; 104 | 105 | Ok(()) 106 | } 107 | } 108 | 109 | impl Bitfield<'_> { 110 | fn verify(&self) -> Result<(), anyhow::Error> { 111 | if self.name.is_empty() { 112 | bail!("unnamed bitfield at offset {}", Hex(self.offset)); 113 | } 114 | 115 | if self.width == 0 { 116 | bail!("bitfield {} has a width of 0 bits", self.name); 117 | } 118 | 119 | Ok(()) 120 | } 121 | } 122 | 123 | fn is_invalid_ident(s: &str) -> bool { 124 | s.contains('%') 125 | } 126 | -------------------------------------------------------------------------------- /host/semidap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "semidap" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | anyhow = "1.0.26" 11 | arrayref = "0.3.6" 12 | binfmt-parser = { path = "../binfmt-parser" } 13 | cm = { path = "../../shared/cm" } 14 | cmsis-dap = { path = "../cmsis-dap" } 15 | colored = "1.9.2" 16 | ctrlc = "3.1.3" 17 | env_logger = "0.7.1" 18 | gimli = "0.20.0" 19 | log = "0.4.8" 20 | rustc-demangle = "0.1.16" 21 | rustyline = "6.0.0" 22 | structopt = "0.3.8" 23 | xmas-elf = "0.7.0" 24 | -------------------------------------------------------------------------------- /host/semiprobe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "semiprobe" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | anyhow = "1.0.31" 11 | arrayref = "0.3.6" 12 | binfmt-parser = { path = "../binfmt-parser" } 13 | cm = { path = "../../shared/cm" } 14 | cmsis-dap = { path = "../cmsis-dap" } 15 | ctrlc = "3.1.4" 16 | env_logger = "0.7.1" 17 | gimli = "0.21.0" 18 | log = "0.4.8" 19 | probe-rs = "0.7.1" 20 | rustc-demangle = "0.1.16" 21 | structopt = "0.3.14" 22 | xmas-elf = "0.7.0" 23 | -------------------------------------------------------------------------------- /host/tasks-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "tasks-macros" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | proc-macro2 = "1.0.10" 14 | quote = "1.0.3" 15 | 16 | [dependencies.syn] 17 | features = ["extra-traits", "full"] 18 | version = "1.0.17" 19 | -------------------------------------------------------------------------------- /shared/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock -------------------------------------------------------------------------------- /shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | # only so we can run `cargo fmt (..)` 2 | [workspace] 3 | members = [ 4 | "binfmt", 5 | "cm", 6 | "consts", 7 | "usb2", 8 | ] -------------------------------------------------------------------------------- /shared/binfmt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "binfmt" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | binfmt-macros = { path = "macros" } 11 | proc-macro-hack = "0.5.11" 12 | -------------------------------------------------------------------------------- /shared/binfmt/README.md: -------------------------------------------------------------------------------- 1 | # `binfmt` 2 | 3 | > The fastest formatter is the one that does no formatting at all 4 | -------------------------------------------------------------------------------- /shared/binfmt/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "binfmt-macros" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | proc-macro-hack = "0.5.11" 14 | proc-macro2 = "1.0.9" 15 | quote = "1.0.2" 16 | rand = "0.7.3" 17 | 18 | [dependencies.syn] 19 | features = ["extra-traits", "full"] 20 | version = "1.0.16" 21 | -------------------------------------------------------------------------------- /shared/binfmt/src/derive.rs: -------------------------------------------------------------------------------- 1 | pub use binfmt_macros::binDebug; 2 | -------------------------------------------------------------------------------- /shared/binfmt/src/impls.rs: -------------------------------------------------------------------------------- 1 | use crate::{binDebug, binWrite, util, Tag}; 2 | 3 | impl binDebug for f32 { 4 | fn fmt(&self, f: &mut impl binWrite) { 5 | f.write_byte(Tag::F32 as u8); 6 | f.write(&self.to_le_bytes()); 7 | } 8 | } 9 | 10 | impl binDebug for i8 { 11 | fn fmt(&self, f: &mut impl binWrite) { 12 | ::fmt(&((*self).into()), f) 13 | } 14 | } 15 | 16 | impl binDebug for i16 { 17 | fn fmt(&self, f: &mut impl binWrite) { 18 | ::fmt(&((*self).into()), f) 19 | } 20 | } 21 | 22 | impl binDebug for i32 { 23 | fn fmt(&self, f: &mut impl binWrite) { 24 | f.write_byte(Tag::Signed as u8); 25 | f.leb128_write(util::zigzag(*self)); 26 | } 27 | } 28 | 29 | impl binDebug for u8 { 30 | fn fmt(&self, f: &mut impl binWrite) { 31 | ::fmt(&((*self).into()), f) 32 | } 33 | } 34 | 35 | impl binDebug for u16 { 36 | fn fmt(&self, f: &mut impl binWrite) { 37 | ::fmt(&((*self).into()), f) 38 | } 39 | } 40 | 41 | impl binDebug for u32 { 42 | fn fmt(&self, f: &mut impl binWrite) { 43 | f.write_byte(Tag::Unsigned as u8); 44 | f.leb128_write(*self); 45 | } 46 | } 47 | 48 | #[cfg(target_pointer_width = "32")] 49 | impl binDebug for *const T { 50 | fn fmt(&self, f: &mut impl binWrite) { 51 | f.write_byte(Tag::Pointer as u8); 52 | f.write(&(*self as u32).to_le_bytes()); 53 | } 54 | } 55 | 56 | #[cfg(target_pointer_width = "32")] 57 | impl binDebug for *mut T { 58 | fn fmt(&self, f: &mut impl binWrite) { 59 | <*const T as binDebug>::fmt(&(*self as *const T), f) 60 | } 61 | } 62 | 63 | #[cfg(target_pointer_width = "32")] 64 | impl binDebug for [u8] { 65 | fn fmt(&self, f: &mut impl binWrite) { 66 | f.write_byte(Tag::Bytes as u8); 67 | f.leb128_write(self.len() as u32); 68 | f.write(self); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /shared/binfmt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | #![no_std] 3 | 4 | pub mod derive; 5 | mod impls; 6 | mod util; 7 | 8 | use proc_macro_hack::proc_macro_hack; 9 | 10 | #[doc(hidden)] 11 | #[proc_macro_hack] 12 | pub use binfmt_macros::binwrite; 13 | 14 | macro_rules! u8_enum { 15 | ($($ident:ident = $expr:expr,)+) => { 16 | #[derive(Clone, Copy, PartialEq)] 17 | #[repr(u8)] 18 | pub enum Tag { 19 | $($ident = $expr,)+ 20 | } 21 | 22 | impl Tag { 23 | pub fn from(byte: u8) -> Option { 24 | Some(match byte { 25 | $($expr => Tag::$ident,)+ 26 | _ => return None, 27 | }) 28 | } 29 | } 30 | } 31 | } 32 | 33 | u8_enum! { 34 | // NOTE values must match the values in the `Level` enum 35 | Error = 0, 36 | Warn = 1, 37 | Info = 2, 38 | Debug = 3, 39 | Trace = 4, 40 | Footprint = 5, 41 | Unsigned = 6, 42 | Signed = 7, 43 | F32 = 8, 44 | Pointer = 9, 45 | Register = 10, 46 | Bytes = 11, 47 | CLikeEnum = 12, 48 | } 49 | 50 | #[repr(u8)] 51 | pub enum Level { 52 | Error = 0, 53 | Warn = 1, 54 | Info = 2, 55 | Debug = 3, 56 | Trace = 4, 57 | } 58 | 59 | #[allow(non_camel_case_types)] 60 | pub trait binDebug { 61 | fn fmt(&self, f: &mut impl binWrite); 62 | } 63 | 64 | const CONTINUE: u8 = 1 << 7; 65 | 66 | #[allow(non_camel_case_types)] 67 | pub trait binWrite: Sized { 68 | fn write_byte(&mut self, byte: u8); 69 | 70 | fn write(&mut self, bytes: &[u8]); 71 | 72 | fn leb128_write(&mut self, mut word: u32) { 73 | loop { 74 | let mut byte = (word & 0x7f) as u8; 75 | word >>= 7; 76 | 77 | if word != 0 { 78 | byte |= CONTINUE; 79 | } 80 | self.write_byte(byte); 81 | 82 | if word == 0 { 83 | return; 84 | } 85 | } 86 | } 87 | 88 | fn write_sym(&mut self, sym: *const u8) { 89 | let sym = sym as u16; 90 | if sym < 127 { 91 | self.write_byte(sym as u8); 92 | } else { 93 | self.write_byte(sym as u8 | CONTINUE); 94 | self.write_byte((sym >> 7) as u8); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /shared/binfmt/src/util.rs: -------------------------------------------------------------------------------- 1 | pub fn zigzag(x: i32) -> u32 { 2 | ((x << 1) ^ (x >> 31)) as u32 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn zigzag() { 9 | assert_eq!(super::zigzag(0), 0); 10 | assert_eq!(super::zigzag(-1), 0b001); 11 | assert_eq!(super::zigzag(1), 0b010); 12 | assert_eq!(super::zigzag(-2), 0b011); 13 | assert_eq!(super::zigzag(2), 0b100); 14 | assert_eq!(super::zigzag(i32::min_value()), 0xffffffff); 15 | assert_eq!(super::zigzag(i32::max_value()), 0xfffffffe); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /shared/cm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "cm" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies.binfmt] 10 | optional = true 11 | path = "../binfmt" 12 | 13 | [features] 14 | DCB = [] 15 | DWT = [] 16 | NVIC = [] 17 | SCB = [] 18 | # mainly used to generate docs 19 | all = ["DCB", "DWT", "NVIC", "SCB"] -------------------------------------------------------------------------------- /shared/cm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | compile_error!("run the `regen` tool before compiling this crate"); 4 | -------------------------------------------------------------------------------- /shared/consts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "consts" 6 | publish = false 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /shared/consts/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | // use some random date as the VID:PID pair 4 | pub const VID: u16 = 0x2020; 5 | pub const PID: u16 = 0x0309; 6 | -------------------------------------------------------------------------------- /shared/usb2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | license = "MIT OR Apache-2.0" 5 | name = "usb2" 6 | publish = false 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | binfmt = { path = "../binfmt" } 11 | -------------------------------------------------------------------------------- /shared/usb2/src/cdc.rs: -------------------------------------------------------------------------------- 1 | //! Communication Device Class functional descriptors 2 | 3 | pub mod acm; 4 | pub mod call; 5 | pub mod header; 6 | pub mod union; 7 | 8 | const CS_INTERFACE: u8 = 0x24; 9 | 10 | const SUBTYPE_HEADER: u8 = 0x00; 11 | const SUBTYPE_CALL: u8 = 0x01; 12 | const SUBTYPE_ACM: u8 = 0x02; 13 | const SUBTYPE_UNION: u8 = 0x06; 14 | -------------------------------------------------------------------------------- /shared/usb2/src/cdc/acm.rs: -------------------------------------------------------------------------------- 1 | //! Abstract Control Management functional descriptor 2 | 3 | /// Abstract Control Management functional descriptor 4 | #[allow(non_snake_case)] 5 | #[derive(Clone, Copy)] 6 | pub struct Desc { 7 | // bFunctionLength: u8, 8 | // bDescriptorType: u8, 9 | // bDescriptorSubtype: u8, 10 | /// Capabilities 11 | pub bmCapabilities: Capabilities, 12 | } 13 | 14 | /// Capabilities 15 | #[derive(Clone, Copy)] 16 | pub struct Capabilities { 17 | /// Device supports `{Set,Clear,Get}_Comm_Feature` 18 | pub comm_features: bool, 19 | /// Device supports `{Set,Get}_Line_Coding`, `Set_Control_Line_State` and `Serial_State` 20 | pub line_serial: bool, 21 | /// Device supports `Send_Break` 22 | pub send_break: bool, 23 | /// Device supports `Network_Connection` 24 | pub network_connection: bool, 25 | } 26 | 27 | impl Capabilities { 28 | fn byte(&self) -> u8 { 29 | let mut byte = 0; 30 | if self.comm_features { 31 | byte |= 1 << 0; 32 | } 33 | if self.line_serial { 34 | byte |= 1 << 1; 35 | } 36 | if self.send_break { 37 | byte |= 1 << 2; 38 | } 39 | if self.network_connection { 40 | byte |= 1 << 3; 41 | } 42 | byte 43 | } 44 | } 45 | 46 | impl Desc { 47 | /// Size of this descriptor on the wire 48 | pub const SIZE: u8 = 4; 49 | 50 | /// Returns the wire representation of this device endpoint 51 | pub fn bytes(&self) -> [u8; Self::SIZE as usize] { 52 | [ 53 | Self::SIZE, 54 | super::CS_INTERFACE, 55 | super::SUBTYPE_ACM, 56 | self.bmCapabilities.byte(), 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /shared/usb2/src/cdc/call.rs: -------------------------------------------------------------------------------- 1 | //! Call Management functional descriptor 2 | 3 | /// Call Management functional descriptor 4 | #[allow(non_snake_case)] 5 | #[derive(Clone, Copy)] 6 | pub struct Desc { 7 | // bFunctionLength: u8, 8 | // bDescriptorType: u8, 9 | // bDescriptorSubtype: u8, 10 | /// Capabilities 11 | pub bmCapabilities: Capabilities, 12 | /// Interface of the Data Class interface 13 | pub bDataInterface: u8, 14 | } 15 | 16 | /// Capabilities 17 | #[derive(Clone, Copy)] 18 | pub struct Capabilities { 19 | /// Device handles call management itself 20 | pub call_management: bool, 21 | /// Device can send/receive call management information over a Data Class interface 22 | pub data_class: bool, 23 | } 24 | 25 | impl Capabilities { 26 | fn byte(&self) -> u8 { 27 | let mut byte = 0; 28 | if self.call_management { 29 | byte |= 1 << 0; 30 | } 31 | if self.data_class { 32 | byte |= 1 << 1; 33 | } 34 | byte 35 | } 36 | } 37 | 38 | impl Desc { 39 | /// Size of this descriptor on the wire 40 | pub const SIZE: u8 = 5; 41 | 42 | /// Returns the wire representation of this device endpoint 43 | pub fn bytes(&self) -> [u8; Self::SIZE as usize] { 44 | [ 45 | Self::SIZE, 46 | super::CS_INTERFACE, 47 | super::SUBTYPE_CALL, 48 | self.bmCapabilities.byte(), 49 | self.bDataInterface, 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /shared/usb2/src/cdc/header.rs: -------------------------------------------------------------------------------- 1 | //! Header functional descriptor 2 | 3 | /// Header functional descriptor 4 | #[allow(non_snake_case)] 5 | pub struct Desc { 6 | /// Communications Devices Specification release number 7 | pub bcdCDC: bcdCDC, 8 | } 9 | 10 | /// Communications Devices specification release number 11 | #[allow(non_camel_case_types)] 12 | #[derive(Clone, Copy)] 13 | pub enum bcdCDC { 14 | /// 1.10 15 | V11 = 0x01_10, 16 | } 17 | 18 | impl Desc { 19 | /// The size of this descriptor on the wire 20 | pub const SIZE: u8 = 5; 21 | 22 | /// Returns the wire representation of this device endpoint 23 | pub fn bytes(&self) -> [u8; Self::SIZE as usize] { 24 | [ 25 | Self::SIZE, 26 | super::CS_INTERFACE, 27 | super::SUBTYPE_HEADER, 28 | self.bcdCDC as u16 as u8, 29 | (self.bcdCDC as u16 >> 8) as u8, 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /shared/usb2/src/cdc/union.rs: -------------------------------------------------------------------------------- 1 | //! Union Interface functional descriptor 2 | 3 | /// Union Interface functional descriptor 4 | #[allow(non_snake_case)] 5 | #[derive(Clone, Copy)] 6 | pub struct Desc { 7 | // bFunctionLength: u8, 8 | // bDescriptorType: u8, 9 | // bDescriptorSubtype: u8, 10 | /// Controlling interface 11 | pub bControlInterface: u8, 12 | /// Subordinate interface 13 | pub bSubordinateInterface0: u8, 14 | } 15 | 16 | impl Desc { 17 | /// Size of this descriptor on the wire 18 | pub const SIZE: u8 = 5; 19 | 20 | /// Returns the wire representation of this device endpoint 21 | pub fn bytes(&self) -> [u8; Self::SIZE as usize] { 22 | [ 23 | Self::SIZE, 24 | super::CS_INTERFACE, 25 | super::SUBTYPE_UNION, 26 | self.bControlInterface, 27 | self.bSubordinateInterface0, 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /shared/usb2/src/config.rs: -------------------------------------------------------------------------------- 1 | //! Configuration descriptor 2 | 3 | use crate::DescriptorType; 4 | 5 | /// Configuration Descriptor 6 | #[allow(non_snake_case)] 7 | #[derive(Clone, Copy)] 8 | pub struct Desc { 9 | // pub blength: u8, 10 | // pub bDescriptorType: u8, 11 | /// The total length of this configuration descriptor plus the descriptors (interface, endpoint, 12 | /// etc.) below it 13 | pub wTotalLength: u16, 14 | /// Number of interfaces associated to this configuration 15 | pub bNumInterfaces: u8, 16 | /// Configuration value 17 | pub bConfigurationValue: u8, 18 | /// Configuration string index 19 | pub iConfiguration: u8, 20 | /// Attributes 21 | pub bmAttributes: bmAttributes, 22 | /// Maximum power (1 ULP = 2 mA) 23 | pub bMaxPower: u8, 24 | } 25 | 26 | impl Desc { 27 | /// The size of this descriptor on the wire 28 | pub const SIZE: u8 = 9; 29 | 30 | /// Returns the wire representation of this descriptor 31 | pub fn bytes(&self) -> [u8; Self::SIZE as usize] { 32 | [ 33 | Self::SIZE, 34 | DescriptorType::CONFIGURATION as u8, 35 | self.wTotalLength as u8, 36 | (self.wTotalLength >> 8) as u8, 37 | self.bNumInterfaces, 38 | self.bConfigurationValue, 39 | self.iConfiguration, 40 | (1 << 7) 41 | | if self.bmAttributes.self_powered { 42 | 1 << 6 43 | } else { 44 | 0 45 | } 46 | | if self.bmAttributes.remote_wakeup { 47 | 1 << 5 48 | } else { 49 | 0 50 | }, 51 | self.bMaxPower, 52 | ] 53 | } 54 | } 55 | 56 | /// Attributes 57 | #[allow(non_camel_case_types)] 58 | #[derive(Clone, Copy)] 59 | pub struct bmAttributes { 60 | /// Self-powered? 61 | pub self_powered: bool, 62 | /// Remote wakeup 63 | pub remote_wakeup: bool, 64 | } 65 | -------------------------------------------------------------------------------- /shared/usb2/src/device.rs: -------------------------------------------------------------------------------- 1 | //! Device descriptor 2 | 3 | use crate::DescriptorType; 4 | 5 | /// Standard Device Descriptor 6 | #[allow(non_snake_case)] 7 | #[derive(Clone, Copy)] 8 | pub struct Desc { 9 | // pub blength: u8, 10 | // pub bDescriptorType: u8, 11 | /// USB specification release version 12 | pub bcdUSB: bcdUSB, 13 | /// Device class 14 | pub bDeviceClass: u8, 15 | /// Device subclass 16 | pub bDeviceSubClass: u8, 17 | /// Device protocol 18 | pub bDeviceProtocol: u8, 19 | /// Maximum packet size 20 | pub bMaxPacketSize0: bMaxPacketSize0, 21 | /// Vendor ID 22 | pub idVendor: u16, 23 | /// Product ID 24 | pub idProduct: u16, 25 | /// Device release number 26 | pub bcdDevice: u16, 27 | /// Manufacturer string index 28 | pub iManufacturer: u8, 29 | /// Product string index 30 | pub iProduct: u8, 31 | /// Serial number string index 32 | pub iSerialNumber: u8, 33 | /// Number of configurations 34 | pub bNumConfigurations: u8, 35 | } 36 | 37 | /// USB specification release version 38 | #[allow(non_camel_case_types)] 39 | #[derive(Clone, Copy)] 40 | pub enum bcdUSB { 41 | /// 2.0 42 | V20 = 0x0200, 43 | // TODO(?) other versions 44 | } 45 | 46 | /// Maximum packet size 47 | #[allow(non_camel_case_types)] 48 | #[derive(Clone, Copy)] 49 | pub enum bMaxPacketSize0 { 50 | /// 8 bytes 51 | B8 = 8, 52 | /// 16 bytes 53 | B16 = 16, 54 | /// 32 bytes 55 | B32 = 32, 56 | /// 64 bytes 57 | B64 = 64, 58 | } 59 | 60 | impl Desc { 61 | /// The size of this descriptor on the wire 62 | pub const SIZE: u8 = 18; 63 | 64 | /// Returns the wire representation of this device endpoint 65 | pub fn bytes(&self) -> [u8; Self::SIZE as usize] { 66 | [ 67 | Self::SIZE, 68 | DescriptorType::DEVICE as u8, 69 | self.bcdUSB as u16 as u8, 70 | (self.bcdUSB as u16 >> 8) as u8, 71 | self.bDeviceClass, 72 | self.bDeviceSubClass, 73 | self.bDeviceProtocol, 74 | self.bMaxPacketSize0 as u8, 75 | self.idVendor as u8, 76 | (self.idVendor >> 8) as u8, 77 | self.idProduct as u8, 78 | (self.idProduct >> 8) as u8, 79 | self.bcdDevice as u8, 80 | (self.bcdDevice >> 8) as u8, 81 | self.iManufacturer, 82 | self.iProduct, 83 | self.iSerialNumber, 84 | self.bNumConfigurations, 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /shared/usb2/src/ep.rs: -------------------------------------------------------------------------------- 1 | //! Endpoints 2 | 3 | use crate::{DescriptorType, Direction}; 4 | 5 | /// Endpoint Descriptor 6 | #[allow(non_snake_case)] 7 | #[derive(Clone, Copy)] 8 | pub struct Desc { 9 | // pub bLength: u8, 10 | // pub bDescriptorType: u8, 11 | /// Endpoint address 12 | pub bEndpointAddress: Address, 13 | /// Attributes 14 | pub bmAttributes: bmAttributes, 15 | /// Maximum packet size 16 | pub wMaxPacketSize: wMaxPacketSize, 17 | /// Polling interval 18 | pub bInterval: u8, 19 | } 20 | 21 | impl Desc { 22 | /// The size of this descriptor on the wire 23 | pub const SIZE: u8 = 7; 24 | 25 | /// Returns the wire representation of this descriptor 26 | pub fn bytes(&self) -> [u8; Self::SIZE as usize] { 27 | let word = self.wMaxPacketSize.word(); 28 | [ 29 | Self::SIZE, 30 | DescriptorType::ENDPOINT as u8, 31 | self.bEndpointAddress.byte(), 32 | self.bmAttributes.byte(), 33 | word as u8, 34 | (word >> 8) as u8, 35 | self.bInterval, 36 | ] 37 | } 38 | } 39 | 40 | /// Endpoint address 41 | #[derive(Clone, Copy)] 42 | pub struct Address { 43 | /// Endpoint number 44 | pub number: u8, 45 | /// Endpoint direction 46 | pub direction: Direction, 47 | } 48 | 49 | impl Address { 50 | fn byte(&self) -> u8 { 51 | (self.number & 0b1111) | (self.direction as u8) << 7 52 | } 53 | } 54 | 55 | /// Endpoint attributes 56 | #[allow(non_camel_case_types)] 57 | #[derive(Clone, Copy)] 58 | pub enum bmAttributes { 59 | /// Bulk endpoint 60 | Bulk, 61 | /// Control endpoint 62 | Control, 63 | /// Interrupt endpoint 64 | Interrupt, 65 | /// Isochronous endpoint 66 | Isochronous { 67 | /// Synchronization type 68 | synchronization_type: SynchronizationType, 69 | /// Usage type 70 | usage_type: UsageType, 71 | }, 72 | } 73 | 74 | impl bmAttributes { 75 | fn byte(&self) -> u8 { 76 | match self { 77 | bmAttributes::Bulk => 0b10, 78 | bmAttributes::Control => 0b00, 79 | bmAttributes::Interrupt => 0b11, 80 | bmAttributes::Isochronous { 81 | synchronization_type, 82 | usage_type, 83 | } => 0b01 | (*synchronization_type as u8) << 2 | (*usage_type as u8) << 4, 84 | } 85 | } 86 | } 87 | 88 | /// Endpoint transfer type 89 | #[derive(Clone, Copy)] 90 | pub enum TransferType { 91 | /// Control endpoint 92 | Control = 0b00, 93 | /// Isochronous endpoint 94 | Isochronous = 0b01, 95 | /// Bulk endpoint 96 | Bulk = 0b10, 97 | /// Interrupt endpoint 98 | Interrupt = 0b11, 99 | } 100 | 101 | /// Synchronization type 102 | #[derive(Clone, Copy)] 103 | pub enum SynchronizationType { 104 | /// No synchronization 105 | NoSynchronization = 0b00, 106 | /// Asynchronous 107 | Asynchronous = 0b01, 108 | /// Adaptive 109 | Adaptive = 0b10, 110 | /// Synchronous 111 | Synchronous = 0b11, 112 | } 113 | 114 | /// Usage type 115 | #[derive(Clone, Copy)] 116 | pub enum UsageType { 117 | /// Data endpoint 118 | DataEndpoint = 0b00, 119 | /// Feedback endpoint 120 | FeedbackEndpoint = 0b01, 121 | /// Implicit feedback data endpoint 122 | ImplicitFeedbackDataEndpoint = 0b10, 123 | } 124 | 125 | /// Maximum packet size 126 | #[allow(non_camel_case_types)] 127 | #[derive(Clone, Copy)] 128 | pub enum wMaxPacketSize { 129 | /// Bulk or control endpoint 130 | BulkControl { 131 | /// Must be less than `1 << 11` 132 | size: u16, 133 | }, 134 | 135 | /// Isochronous or interrupt endpoint 136 | IsochronousInterrupt { 137 | /// Must be less than `1 << 11` 138 | size: u16, 139 | /// Transactions per microframe 140 | transactions_per_microframe: Transactions, 141 | }, 142 | } 143 | 144 | /// Transactions per microframe 145 | #[derive(Clone, Copy)] 146 | pub enum Transactions { 147 | /// 1 transaction per microframe 148 | _1 = 0b00, 149 | /// 2 transactions per microframe 150 | _2 = 0b01, 151 | /// 3 transactions per microframe 152 | _3 = 0b10, 153 | } 154 | 155 | impl wMaxPacketSize { 156 | fn word(&self) -> u16 { 157 | match self { 158 | wMaxPacketSize::BulkControl { size } => *size & ((1 << 11) - 1), 159 | 160 | wMaxPacketSize::IsochronousInterrupt { 161 | size, 162 | transactions_per_microframe, 163 | } => (*size & ((1 << 11) - 1)) | ((*transactions_per_microframe as u16) << 11), 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /shared/usb2/src/iface.rs: -------------------------------------------------------------------------------- 1 | //! Interface descriptors 2 | 3 | use crate::DescriptorType; 4 | 5 | /// Interface Descriptor 6 | #[allow(non_snake_case)] 7 | #[derive(Clone, Copy)] 8 | pub struct Desc { 9 | // pub bLength: u8, 10 | // pub bDescriptorType: u8, 11 | /// Interface number 12 | pub bInterfaceNumber: u8, 13 | /// Alternative setting 14 | pub bAlternativeSetting: u8, 15 | /// Number of endpoints 16 | pub bNumEndpoints: u8, 17 | /// Interface class 18 | pub bInterfaceClass: Class, 19 | /// Interface protocol 20 | pub bInterfaceProtocol: u8, 21 | /// Interface string descriptor index 22 | pub iInterface: u8, 23 | } 24 | 25 | /// Interface class 26 | #[derive(Clone, Copy)] 27 | pub enum Class { 28 | /// Communications class 29 | Communications { 30 | /// Subclass 31 | subclass: CommunicationsSubclass, 32 | }, 33 | 34 | /// Communication Data class 35 | CdcData, 36 | } 37 | 38 | /// Sub-classes of the Communications class 39 | #[derive(Clone, Copy)] 40 | pub enum CommunicationsSubclass { 41 | /// Abstract Control Mode 42 | Acm = 0x2, 43 | } 44 | 45 | impl Class { 46 | fn byte(&self) -> u8 { 47 | match self { 48 | Class::Communications { .. } => 0x2, 49 | Class::CdcData => 0xA, 50 | } 51 | } 52 | 53 | fn subclass_byte(&self) -> u8 { 54 | match self { 55 | Class::Communications { subclass } => *subclass as u8, 56 | Class::CdcData => 0, 57 | } 58 | } 59 | } 60 | 61 | impl Desc { 62 | /// The size of this descriptor in bytes 63 | pub const SIZE: u8 = 9; 64 | 65 | /// Returns the byte representation of this descriptor 66 | pub fn bytes(&self) -> [u8; Self::SIZE as usize] { 67 | [ 68 | Self::SIZE, 69 | DescriptorType::INTERFACE as u8, 70 | self.bInterfaceNumber, 71 | self.bAlternativeSetting, 72 | self.bNumEndpoints, 73 | self.bInterfaceClass.byte(), 74 | self.bInterfaceClass.subclass_byte(), 75 | self.bInterfaceProtocol, 76 | self.iInterface, 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /shared/usb2/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! USB 2.0 definitions 2 | //! 3 | //! # References 4 | //! 5 | //! - Universal Serial Bus Specification Revision 2.0 6 | //! - Universal Serial Bus Communications Class Subclass Specification for PSTN Devices Revision 1.2 7 | //! - Universal Serial Bus Class Definitions for Communications Devices Revision 1.2 (Errata 1) 8 | 9 | #![deny(missing_docs)] 10 | #![deny(warnings)] 11 | #![no_std] 12 | 13 | use core::convert::TryFrom; 14 | 15 | use binfmt::derive::binDebug; 16 | 17 | pub mod cdc; 18 | pub mod config; 19 | pub mod device; 20 | pub mod ep; 21 | pub mod iface; 22 | 23 | /// USB direction (from the point of view of the host) 24 | #[derive(binDebug, Clone, Copy, PartialEq)] 25 | pub enum Direction { 26 | /// Host-to-Device 27 | OUT = 0, 28 | 29 | /// Device-to-Host 30 | IN = 1, 31 | } 32 | 33 | /// USB device state 34 | #[derive(Clone, Copy, PartialEq)] 35 | pub enum State { 36 | /// Default state 37 | Default, 38 | /// Addressed state 39 | Address, 40 | /// Configured state 41 | Configured { 42 | /// Configuration value 43 | configuration: u8, 44 | }, 45 | } 46 | 47 | /// Standard Request Code 48 | // see table 9-4 Standard Request Codes 49 | #[allow(non_camel_case_types)] 50 | #[derive(Clone, Copy, PartialEq)] 51 | pub enum bRequest { 52 | /// Get status 53 | GET_STATUS, 54 | /// Clear feature 55 | CLEAR_FEATURE, 56 | /// Set feature 57 | SET_FEATURE, 58 | /// Set address 59 | SET_ADDRESS, 60 | /// Get descriptor 61 | GET_DESCRIPTOR, 62 | /// Set descriptor 63 | SET_DESCRIPTOR, 64 | /// Get configuration 65 | GET_CONFIGURATION, 66 | /// Set configuration 67 | SET_CONFIGURATION, 68 | /// Get interface 69 | GET_INTERFACE, 70 | /// Set interface 71 | SET_INTERFACE, 72 | /// Synch frame 73 | SYNCH_FRAME, 74 | /// Reserved request code 75 | Reserved, 76 | /// Non-standard request code 77 | NonStandard(u8), 78 | } 79 | 80 | impl From for bRequest { 81 | fn from(byte: u8) -> Self { 82 | match byte { 83 | 0 => bRequest::GET_STATUS, 84 | 1 => bRequest::CLEAR_FEATURE, 85 | 2 => bRequest::Reserved, 86 | 3 => bRequest::SET_FEATURE, 87 | 4 => bRequest::Reserved, 88 | 5 => bRequest::SET_ADDRESS, 89 | 6 => bRequest::GET_DESCRIPTOR, 90 | 7 => bRequest::SET_DESCRIPTOR, 91 | 8 => bRequest::GET_CONFIGURATION, 92 | 9 => bRequest::SET_CONFIGURATION, 93 | 10 => bRequest::GET_INTERFACE, 94 | 11 => bRequest::SET_INTERFACE, 95 | 12 => bRequest::SYNCH_FRAME, 96 | byte => bRequest::NonStandard(byte), 97 | } 98 | } 99 | } 100 | 101 | /// Descriptor type 102 | #[allow(non_camel_case_types)] 103 | #[derive(binDebug, Clone, Copy, PartialEq)] 104 | pub enum DescriptorType { 105 | /// Device descriptor 106 | DEVICE = 1, 107 | /// Configuration descriptor 108 | CONFIGURATION = 2, 109 | /// String descriptor 110 | STRING = 3, 111 | /// Interface descriptor 112 | INTERFACE = 4, 113 | /// Endpoint descriptor 114 | ENDPOINT = 5, 115 | /// Device qualifier descriptor 116 | DEVICE_QUALIFIER = 6, 117 | /// Other speed configuration descriptor 118 | OTHER_SPEED_CONFIGURATION = 7, 119 | /// Interface power descriptor 120 | INTERFACE_POWER = 8, 121 | } 122 | 123 | impl TryFrom for DescriptorType { 124 | type Error = (); 125 | fn try_from(byte: u8) -> Result { 126 | Ok(match byte { 127 | 1 => DescriptorType::DEVICE, 128 | 2 => DescriptorType::CONFIGURATION, 129 | 3 => DescriptorType::STRING, 130 | 4 => DescriptorType::INTERFACE, 131 | 5 => DescriptorType::ENDPOINT, 132 | 6 => DescriptorType::DEVICE_QUALIFIER, 133 | 7 => DescriptorType::OTHER_SPEED_CONFIGURATION, 134 | 8 => DescriptorType::INTERFACE_POWER, 135 | _ => return Err(()), 136 | }) 137 | } 138 | } 139 | --------------------------------------------------------------------------------