commit 9a57deef9b531b8dae9d98a5accf5aeb128ab86d Author: Dario Nieuwenhuis Date: Tue Sep 22 18:03:43 2020 +0200 First commit diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 000000000..3f319ae55 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,27 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip nRF52840_xxAA --defmt" + +rustflags = [ + # LLD (shipped with the Rust toolchain) is used as the default linker + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + + # if you run into problems with LLD switch to the GNU linker by commenting out + # this line + # "-C", "linker=arm-none-eabi-ld", + + # if you need to link to pre-compiled C libraries provided by a C toolchain + # use GCC as the linker by commenting out both lines above and then + # uncommenting the three lines below + # "-C", "linker=arm-none-eabi-gcc", + # "-C", "link-arg=-Wl,-Tlink.x", + # "-C", "link-arg=-nostartfiles", +] + +[build] +# Pick ONE of these compilation targets +# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ +# target = "thumbv7m-none-eabi" # Cortex-M3 +# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..ef95cf96e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "editor.formatOnSave": true, + "rust-analyzer.cargo.allFeatures": false, + "rust-analyzer.checkOnSave.allFeatures": false, + "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", + "rust-analyzer.checkOnSave.allTargets": false, + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/target/**": true + } +} diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..4515b020c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,46 @@ + +[workspace] +members = [ + "embassy", + "embassy-nrf", + "examples", +] + +[patch.crates-io] +panic-probe = { git = "https://github.com/knurling-rs/probe-run", branch="main" } +defmt-rtt = { git = "https://github.com/knurling-rs/defmt", branch="cursed-symbol-names-linkers-must-repent-for-their-sins" } +defmt = { git = "https://github.com/knurling-rs/defmt", branch="cursed-symbol-names-linkers-must-repent-for-their-sins" } +static-executor = { git = "https://github.com/Dirbaio/static-executor" } +static-executor-cortex-m = { git = "https://github.com/Dirbaio/static-executor" } + +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true +incremental = false +opt-level = 3 +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 3 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..dacc57b2b --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020 Dario Nieuwenhuis + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..ffa60a864 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Embassy + +Embassy is a project to make async/await a first-class option for embedded development. + +The `embassy` crate defines some traits. + +- `embassy::io`: Traits for byte-stream IO, essentially `no_std` compatible versions of `futures::io`. +- `embassy::flash`: Trait for an async flash device. +- More traits for SPI, I2C, UART async HAL coming soon. + +The `embassy-nrf` crate contains implementations for nRF 52 series SoCs. + +- `uarte`: UARTE driver implementing `AsyncBufRead` and `AsyncWrite`. +- `qspi`: QSPI driver implementing `Flash`. + +Currently Embassy requires a recent nightly, mainly for `generic_associated_types` (for trait funcs returning futures) and `type_alias_impl_trait` (for returning futures implemented with `async{}` blocks). Stable support is a non-goal. + +## Why the name? + +EMBedded ASYnc. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml new file mode 100644 index 000000000..b367de839 --- /dev/null +++ b/embassy-nrf/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "embassy-nrf" +version = "0.1.0" +authors = ["Dario Nieuwenhuis "] +edition = "2018" + +[features] +default = [ + "defmt-default", +] +defmt-default = [] +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + +nrf52810 = ["nrf52810-pac"] +nrf52811 = ["nrf52811-pac"] +nrf52832 = ["nrf52832-pac"] +nrf52833 = ["nrf52833-pac"] +nrf52840 = ["nrf52840-pac"] + + +[dependencies] +embassy = { version = "0.1.0", path = "../embassy" } +cortex-m-rt = "0.6.12" +cortex-m = { version = "0.6.3" } +embedded-hal = { version = "0.2.4" } +nrf52840-hal = { version = "0.11.0" } +bare-metal = { version = "0.2.0", features = ["const-fn"] } +defmt = "0.1.0" + +nrf52810-pac = { version = "0.9.0", optional = true } +nrf52811-pac = { version = "0.9.0", optional = true } +nrf52832-pac = { version = "0.9.0", optional = true } +nrf52833-pac = { version = "0.9.0", optional = true } +nrf52840-pac = { version = "0.9.0", optional = true } diff --git a/embassy-nrf/src/interrupt.rs b/embassy-nrf/src/interrupt.rs new file mode 100644 index 000000000..e227032cb --- /dev/null +++ b/embassy-nrf/src/interrupt.rs @@ -0,0 +1,131 @@ +//! Interrupt management +//! +//! This module implements an API for managing interrupts compatible with +//! nrf_softdevice::interrupt. Intended for switching between the two at compile-time. + +use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; + +use crate::pac::{NVIC, NVIC_PRIO_BITS}; + +// Re-exports +pub use crate::pac::Interrupt; +pub use crate::pac::Interrupt::*; // needed for cortex-m-rt #[interrupt] +pub use bare_metal::{CriticalSection, Mutex}; + +#[derive(defmt::Format, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[repr(u8)] +pub enum Priority { + Level0 = 0, + Level1 = 1, + Level2 = 2, + Level3 = 3, + Level4 = 4, + Level5 = 5, + Level6 = 6, + Level7 = 7, +} + +impl Priority { + #[inline] + fn to_nvic(self) -> u8 { + (self as u8) << (8 - NVIC_PRIO_BITS) + } + + #[inline] + fn from_nvic(priority: u8) -> Self { + match priority >> (8 - NVIC_PRIO_BITS) { + 0 => Self::Level0, + 1 => Self::Level1, + 2 => Self::Level2, + 3 => Self::Level3, + 4 => Self::Level4, + 5 => Self::Level5, + 6 => Self::Level6, + 7 => Self::Level7, + _ => unreachable!(), + } + } +} + +static CS_FLAG: AtomicBool = AtomicBool::new(false); +static mut CS_MASK: [u32; 2] = [0; 2]; + +#[inline] +pub fn free(f: F) -> R +where + F: FnOnce(&CriticalSection) -> R, +{ + unsafe { + // TODO: assert that we're in privileged level + // Needed because disabling irqs in non-privileged level is a noop, which would break safety. + + let primask: u32; + asm!("mrs {}, PRIMASK", out(reg) primask); + + asm!("cpsid i"); + + // Prevent compiler from reordering operations inside/outside the critical section. + compiler_fence(Ordering::SeqCst); + + let r = f(&CriticalSection::new()); + + compiler_fence(Ordering::SeqCst); + + if primask & 1 == 0 { + asm!("cpsie i"); + } + + r + } +} + +#[inline] +pub fn enable(irq: Interrupt) { + unsafe { + NVIC::unmask(irq); + } +} + +#[inline] +pub fn disable(irq: Interrupt) { + NVIC::mask(irq); +} + +#[inline] +pub fn is_active(irq: Interrupt) -> bool { + NVIC::is_active(irq) +} + +#[inline] +pub fn is_enabled(irq: Interrupt) -> bool { + NVIC::is_enabled(irq) +} + +#[inline] +pub fn is_pending(irq: Interrupt) -> bool { + NVIC::is_pending(irq) +} + +#[inline] +pub fn pend(irq: Interrupt) { + NVIC::pend(irq) +} + +#[inline] +pub fn unpend(irq: Interrupt) { + NVIC::unpend(irq) +} + +#[inline] +pub fn get_priority(irq: Interrupt) -> Priority { + Priority::from_nvic(NVIC::get_priority(irq)) +} + +#[inline] +pub fn set_priority(irq: Interrupt, prio: Priority) { + unsafe { + cortex_m::peripheral::Peripherals::steal() + .NVIC + .set_priority(irq, prio.to_nvic()) + } +} diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs new file mode 100644 index 000000000..f1ce0cbf9 --- /dev/null +++ b/embassy-nrf/src/lib.rs @@ -0,0 +1,43 @@ +#![no_std] +#![feature(generic_associated_types)] +#![feature(asm)] +#![feature(type_alias_impl_trait)] + +#[cfg(not(any( + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", +)))] +compile_error!("No chip feature activated. You must activate exactly one of the following features: nrf52810, nrf52811, nrf52832, nrf52833, nrf52840"); + +#[cfg(any( + all(feature = "nrf52810", feature = "nrf52811"), + all(feature = "nrf52810", feature = "nrf52832"), + all(feature = "nrf52810", feature = "nrf52833"), + all(feature = "nrf52810", feature = "nrf52840"), + all(feature = "nrf52811", feature = "nrf52832"), + all(feature = "nrf52811", feature = "nrf52833"), + all(feature = "nrf52811", feature = "nrf52840"), + all(feature = "nrf52832", feature = "nrf52833"), + all(feature = "nrf52832", feature = "nrf52840"), + all(feature = "nrf52833", feature = "nrf52840"), +))] +compile_error!("Multile chip features activated. You must activate exactly one of the following features: nrf52810, nrf52811, nrf52832, nrf52833, nrf52840"); + +#[cfg(feature = "nrf52810")] +pub use nrf52810_pac as pac; +#[cfg(feature = "nrf52811")] +pub use nrf52811_pac as pac; +#[cfg(feature = "nrf52832")] +pub use nrf52832_pac as pac; +#[cfg(feature = "nrf52833")] +pub use nrf52833_pac as pac; +#[cfg(feature = "nrf52840")] +pub use nrf52840_pac as pac; + +pub mod interrupt; +pub mod qspi; +pub mod uarte; +pub use cortex_m_rt::interrupt; diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs new file mode 100644 index 000000000..d2caddee0 --- /dev/null +++ b/embassy-nrf/src/qspi.rs @@ -0,0 +1,322 @@ +use crate::pac::{Interrupt, QSPI}; +use core::future::Future; +use nrf52840_hal::gpio::{Output, Pin as GpioPin, Port as GpioPort, PushPull}; + +pub use crate::pac::qspi::ifconfig0::ADDRMODE_A as AddressMode; +pub use crate::pac::qspi::ifconfig0::PPSIZE_A as WritePageSize; +pub use crate::pac::qspi::ifconfig0::READOC_A as ReadOpcode; +pub use crate::pac::qspi::ifconfig0::WRITEOC_A as WriteOpcode; + +// TODO +// - config: +// - 32bit address mode +// - SPI freq +// - SPI sck delay +// - Deep power down mode (DPM) +// - SPI mode 3 +// - activate/deactivate +// - set gpio in high drive + +use embassy::flash::{Error, Flash}; +use embassy::util::{DropBomb, Signal}; + +use crate::interrupt; + +pub struct Pins { + pub sck: GpioPin>, + pub csn: GpioPin>, + pub io0: GpioPin>, + pub io1: GpioPin>, + pub io2: Option>>, + pub io3: Option>>, +} + +pub struct Config { + pub pins: Pins, + pub xip_offset: u32, + pub read_opcode: ReadOpcode, + pub write_opcode: WriteOpcode, + pub write_page_size: WritePageSize, +} + +pub struct Qspi { + inner: QSPI, +} + +fn port_bit(port: GpioPort) -> bool { + match port { + GpioPort::Port0 => false, + GpioPort::Port1 => true, + } +} + +impl Qspi { + pub fn new(qspi: QSPI, config: Config) -> Self { + qspi.psel.sck.write(|w| { + let pin = &config.pins.sck; + let w = unsafe { w.pin().bits(pin.pin()) }; + let w = w.port().bit(port_bit(pin.port())); + w.connect().connected() + }); + qspi.psel.csn.write(|w| { + let pin = &config.pins.csn; + let w = unsafe { w.pin().bits(pin.pin()) }; + let w = w.port().bit(port_bit(pin.port())); + w.connect().connected() + }); + qspi.psel.io0.write(|w| { + let pin = &config.pins.io0; + let w = unsafe { w.pin().bits(pin.pin()) }; + let w = w.port().bit(port_bit(pin.port())); + w.connect().connected() + }); + qspi.psel.io1.write(|w| { + let pin = &config.pins.io1; + let w = unsafe { w.pin().bits(pin.pin()) }; + let w = w.port().bit(port_bit(pin.port())); + w.connect().connected() + }); + qspi.psel.io2.write(|w| { + if let Some(ref pin) = config.pins.io2 { + let w = unsafe { w.pin().bits(pin.pin()) }; + let w = w.port().bit(port_bit(pin.port())); + w.connect().connected() + } else { + w.connect().disconnected() + } + }); + qspi.psel.io3.write(|w| { + if let Some(ref pin) = config.pins.io3 { + let w = unsafe { w.pin().bits(pin.pin()) }; + let w = w.port().bit(port_bit(pin.port())); + w.connect().connected() + } else { + w.connect().disconnected() + } + }); + + qspi.ifconfig0.write(|w| { + let w = w.addrmode().variant(AddressMode::_24BIT); + let w = w.dpmenable().disable(); + let w = w.ppsize().variant(config.write_page_size); + let w = w.readoc().variant(config.read_opcode); + let w = w.writeoc().variant(config.write_opcode); + w + }); + + qspi.ifconfig1.write(|w| { + let w = unsafe { w.sckdelay().bits(80) }; + let w = w.dpmen().exit(); + let w = w.spimode().mode0(); + let w = unsafe { w.sckfreq().bits(3) }; + w + }); + + qspi.xipoffset + .write(|w| unsafe { w.xipoffset().bits(config.xip_offset) }); + + // Enable it + qspi.enable.write(|w| w.enable().enabled()); + + qspi.events_ready.reset(); + qspi.tasks_activate.write(|w| w.tasks_activate().bit(true)); + while qspi.events_ready.read().bits() == 0 {} + qspi.events_ready.reset(); + + // Enable READY interrupt + qspi.intenset.write(|w| w.ready().set()); + interrupt::set_priority(Interrupt::QSPI, interrupt::Priority::Level7); + interrupt::enable(Interrupt::QSPI); + + Self { inner: qspi } + } + + pub fn custom_instruction<'a>( + &'a mut self, + opcode: u8, + req: &'a [u8], + resp: &'a mut [u8], + ) -> impl Future> + 'a { + async move { + let bomb = DropBomb::new(); + + assert!(req.len() <= 8); + assert!(resp.len() <= 8); + + let mut dat0: u32 = 0; + let mut dat1: u32 = 0; + + for i in 0..4 { + if i < req.len() { + dat0 |= (req[i] as u32) << (i * 8); + } + } + for i in 0..4 { + if i + 4 < req.len() { + dat1 |= (req[i + 4] as u32) << (i * 8); + } + } + + let len = core::cmp::max(req.len(), resp.len()) as u8; + + self.inner.cinstrdat0.write(|w| unsafe { w.bits(dat0) }); + self.inner.cinstrdat1.write(|w| unsafe { w.bits(dat1) }); + self.inner.events_ready.reset(); + self.inner.cinstrconf.write(|w| { + let w = unsafe { w.opcode().bits(opcode) }; + let w = unsafe { w.length().bits(len + 1) }; + let w = w.lio2().bit(true); + let w = w.lio3().bit(true); + let w = w.wipwait().bit(true); + let w = w.wren().bit(true); + let w = w.lfen().bit(false); + let w = w.lfstop().bit(false); + w + }); + + SIGNAL.wait().await; + + let dat0 = self.inner.cinstrdat0.read().bits(); + let dat1 = self.inner.cinstrdat1.read().bits(); + for i in 0..4 { + if i < resp.len() { + resp[i] = (dat0 >> (i * 8)) as u8; + } + } + for i in 0..4 { + if i + 4 < resp.len() { + resp[i] = (dat1 >> (i * 8)) as u8; + } + } + + bomb.defuse(); + + Ok(()) + } + } +} + +impl Flash for Qspi { + type ReadFuture<'a> = impl Future> + 'a; + type WriteFuture<'a> = impl Future> + 'a; + type ErasePageFuture<'a> = impl Future> + 'a; + + fn read<'a>(&'a mut self, address: usize, data: &'a mut [u8]) -> Self::ReadFuture<'a> { + async move { + let bomb = DropBomb::new(); + + assert_eq!(data.as_ptr() as u32 % 4, 0); + assert_eq!(data.len() as u32 % 4, 0); + assert_eq!(address as u32 % 4, 0); + + self.inner + .read + .src + .write(|w| unsafe { w.src().bits(address as u32) }); + self.inner + .read + .dst + .write(|w| unsafe { w.dst().bits(data.as_ptr() as u32) }); + self.inner + .read + .cnt + .write(|w| unsafe { w.cnt().bits(data.len() as u32) }); + + self.inner.events_ready.reset(); + self.inner + .tasks_readstart + .write(|w| w.tasks_readstart().bit(true)); + + SIGNAL.wait().await; + + bomb.defuse(); + + Ok(()) + } + } + + fn write<'a>(&'a mut self, address: usize, data: &'a [u8]) -> Self::WriteFuture<'a> { + async move { + let bomb = DropBomb::new(); + + assert_eq!(data.as_ptr() as u32 % 4, 0); + assert_eq!(data.len() as u32 % 4, 0); + assert_eq!(address as u32 % 4, 0); + + self.inner + .write + .src + .write(|w| unsafe { w.src().bits(data.as_ptr() as u32) }); + self.inner + .write + .dst + .write(|w| unsafe { w.dst().bits(address as u32) }); + self.inner + .write + .cnt + .write(|w| unsafe { w.cnt().bits(data.len() as u32) }); + + self.inner.events_ready.reset(); + self.inner + .tasks_writestart + .write(|w| w.tasks_writestart().bit(true)); + + SIGNAL.wait().await; + + bomb.defuse(); + + Ok(()) + } + } + + fn erase<'a>(&'a mut self, address: usize) -> Self::ErasePageFuture<'a> { + async move { + let bomb = DropBomb::new(); + + assert_eq!(address as u32 % 4096, 0); + + self.inner + .erase + .ptr + .write(|w| unsafe { w.ptr().bits(address as u32) }); + self.inner.erase.len.write(|w| w.len()._4kb()); + self.inner.events_ready.reset(); + self.inner + .tasks_erasestart + .write(|w| w.tasks_erasestart().bit(true)); + + SIGNAL.wait().await; + + bomb.defuse(); + + Ok(()) + } + } + + fn size(&self) -> usize { + 256 * 4096 // TODO + } + + fn read_size(&self) -> usize { + 4 // TODO + } + + fn write_size(&self) -> usize { + 4 // TODO + } + + fn erase_size(&self) -> usize { + 4096 // TODO + } +} + +static SIGNAL: Signal<()> = Signal::new(); + +#[interrupt] +unsafe fn QSPI() { + let p = unsafe { crate::pac::Peripherals::steal().QSPI }; + if p.events_ready.read().events_ready().bit_is_set() { + p.events_ready.reset(); + SIGNAL.signal(()); + } +} diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs new file mode 100644 index 000000000..b904f006a --- /dev/null +++ b/embassy-nrf/src/uarte.rs @@ -0,0 +1,550 @@ +//! HAL interface to the UARTE peripheral +//! +//! See product specification: +//! +//! - nrf52832: Section 35 +//! - nrf52840: Section 6.34 +use core::cell::UnsafeCell; +use core::cmp::min; +use core::marker::PhantomPinned; +use core::ops::Deref; +use core::pin::Pin; +use core::ptr; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use crate::interrupt; +use crate::interrupt::CriticalSection; +use crate::pac::{uarte0, Interrupt, UARTE0, UARTE1}; +use embedded_hal::digital::v2::OutputPin; +use nrf52840_hal::gpio::{Floating, Input, Output, Pin as GpioPin, Port as GpioPort, PushPull}; + +// Re-export SVD variants to allow user to directly set values +pub use uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity}; + +use embassy::io::{AsyncBufRead, AsyncWrite, Result}; +use embassy::util::WakerStore; + +use defmt::trace; + +//use crate::trace; + +const RINGBUF_SIZE: usize = 512; +struct RingBuf { + buf: [u8; RINGBUF_SIZE], + start: usize, + end: usize, + empty: bool, +} + +impl RingBuf { + fn new() -> Self { + RingBuf { + buf: [0; RINGBUF_SIZE], + start: 0, + end: 0, + empty: true, + } + } + + fn push_buf(&mut self) -> &mut [u8] { + if self.start == self.end && !self.empty { + trace!(" ringbuf: push_buf empty"); + return &mut self.buf[..0]; + } + + let n = if self.start <= self.end { + RINGBUF_SIZE - self.end + } else { + self.start - self.end + }; + + trace!(" ringbuf: push_buf {:?}..{:?}", self.end, self.end + n); + &mut self.buf[self.end..self.end + n] + } + + fn push(&mut self, n: usize) { + trace!(" ringbuf: push {:?}", n); + if n == 0 { + return; + } + + self.end = Self::wrap(self.end + n); + self.empty = false; + } + + fn pop_buf(&mut self) -> &mut [u8] { + if self.empty { + trace!(" ringbuf: pop_buf empty"); + return &mut self.buf[..0]; + } + + let n = if self.end <= self.start { + RINGBUF_SIZE - self.start + } else { + self.end - self.start + }; + + trace!(" ringbuf: pop_buf {:?}..{:?}", self.start, self.start + n); + &mut self.buf[self.start..self.start + n] + } + + fn pop(&mut self, n: usize) { + trace!(" ringbuf: pop {:?}", n); + if n == 0 { + return; + } + + self.start = Self::wrap(self.start + n); + self.empty = self.start == self.end; + } + + fn wrap(n: usize) -> usize { + assert!(n <= RINGBUF_SIZE); + if n == RINGBUF_SIZE { + 0 + } else { + n + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +enum RxState { + Idle, + Receiving, + ReceivingReady, + Stopping, +} +#[derive(Copy, Clone, Debug, PartialEq)] +enum TxState { + Idle, + Transmitting(usize), +} + +/// Interface to a UARTE instance +/// +/// This is a very basic interface that comes with the following limitations: +/// - The UARTE instances share the same address space with instances of UART. +/// You need to make sure that conflicting instances +/// are disabled before using `Uarte`. See product specification: +/// - nrf52832: Section 15.2 +/// - nrf52840: Section 6.1.2 +pub struct Uarte { + started: bool, + state: UnsafeCell>, +} + +// public because it needs to be used in Instance::{get_state, set_state}, but +// should not be used outside the module +#[doc(hidden)] +pub struct UarteState { + inner: T, + + rx: RingBuf, + rx_state: RxState, + rx_waker: WakerStore, + + tx: RingBuf, + tx_state: TxState, + tx_waker: WakerStore, + + _pin: PhantomPinned, +} + +fn port_bit(port: GpioPort) -> bool { + match port { + GpioPort::Port0 => false, + GpioPort::Port1 => true, + } +} + +impl Uarte { + pub fn new(uarte: T, mut pins: Pins, parity: Parity, baudrate: Baudrate) -> Self { + // Select pins + uarte.psel.rxd.write(|w| { + let w = unsafe { w.pin().bits(pins.rxd.pin()) }; + let w = w.port().bit(port_bit(pins.rxd.port())); + w.connect().connected() + }); + pins.txd.set_high().unwrap(); + uarte.psel.txd.write(|w| { + let w = unsafe { w.pin().bits(pins.txd.pin()) }; + let w = w.port().bit(port_bit(pins.txd.port())); + w.connect().connected() + }); + + // Optional pins + uarte.psel.cts.write(|w| { + if let Some(ref pin) = pins.cts { + let w = unsafe { w.pin().bits(pin.pin()) }; + let w = w.port().bit(port_bit(pin.port())); + w.connect().connected() + } else { + w.connect().disconnected() + } + }); + + uarte.psel.rts.write(|w| { + if let Some(ref pin) = pins.rts { + let w = unsafe { w.pin().bits(pin.pin()) }; + let w = w.port().bit(port_bit(pin.port())); + w.connect().connected() + } else { + w.connect().disconnected() + } + }); + + // Enable UARTE instance + uarte.enable.write(|w| w.enable().enabled()); + + // Enable interrupts + uarte.intenset.write(|w| w.endrx().set().endtx().set()); + + // Configure + let hardware_flow_control = pins.rts.is_some() && pins.cts.is_some(); + uarte + .config + .write(|w| w.hwfc().bit(hardware_flow_control).parity().variant(parity)); + + // Configure frequency + uarte.baudrate.write(|w| w.baudrate().variant(baudrate)); + + Uarte { + started: false, + state: UnsafeCell::new(UarteState { + inner: uarte, + + rx: RingBuf::new(), + rx_state: RxState::Idle, + rx_waker: WakerStore::new(), + + tx: RingBuf::new(), + tx_state: TxState::Idle, + tx_waker: WakerStore::new(), + + _pin: PhantomPinned, + }), + } + } + + fn with_state<'a, R>( + self: Pin<&'a mut Self>, + f: impl FnOnce(Pin<&'a mut UarteState>) -> R, + ) -> R { + let Self { state, started } = unsafe { self.get_unchecked_mut() }; + + interrupt::free(|cs| { + let ptr = state.get(); + + if !*started { + T::set_state(cs, ptr); + + *started = true; + + // safety: safe because critical section ensures only one *mut UartState + // exists at the same time. + unsafe { Pin::new_unchecked(&mut *ptr) }.start(); + } + + // safety: safe because critical section ensures only one *mut UartState + // exists at the same time. + f(unsafe { Pin::new_unchecked(&mut *ptr) }) + }) + } +} + +impl Drop for Uarte { + fn drop(&mut self) { + // stop DMA before dropping, because DMA is using the buffer in `self`. + todo!() + } +} + +impl AsyncBufRead for Uarte { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.with_state(|s| s.poll_fill_buf(cx)) + } + + fn consume(self: Pin<&mut Self>, amt: usize) { + self.with_state(|s| s.consume(amt)) + } +} + +impl AsyncWrite for Uarte { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + self.with_state(|s| s.poll_write(cx, buf)) + } +} + +impl UarteState { + pub fn start(self: Pin<&mut Self>) { + interrupt::set_priority(T::interrupt(), interrupt::Priority::Level7); + interrupt::enable(T::interrupt()); + interrupt::pend(T::interrupt()); + } + + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = unsafe { self.get_unchecked_mut() }; + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // before any DMA action has started + compiler_fence(Ordering::SeqCst); + trace!("poll_read"); + + // We have data ready in buffer? Return it. + let buf = this.rx.pop_buf(); + if buf.len() != 0 { + trace!(" got {:?} {:?}", buf.as_ptr() as u32, buf.len()); + return Poll::Ready(Ok(buf)); + } + + trace!(" empty"); + + if this.rx_state == RxState::ReceivingReady { + trace!(" stopping"); + this.rx_state = RxState::Stopping; + this.inner.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + } + + this.rx_waker.store(cx.waker()); + Poll::Pending + } + + fn consume(self: Pin<&mut Self>, amt: usize) { + let this = unsafe { self.get_unchecked_mut() }; + trace!("consume {:?}", amt); + this.rx.pop(amt); + interrupt::pend(T::interrupt()); + } + + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + let this = unsafe { self.get_unchecked_mut() }; + + trace!("poll_write: {:?}", buf.len()); + + let tx_buf = this.tx.push_buf(); + if tx_buf.len() == 0 { + trace!("poll_write: pending"); + this.tx_waker.store(cx.waker()); + return Poll::Pending; + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + this.tx.push(n); + + trace!("poll_write: queued {:?}", n); + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // before any DMA action has started + compiler_fence(Ordering::SeqCst); + + interrupt::pend(T::interrupt()); + + Poll::Ready(Ok(n)) + } + + fn on_interrupt(&mut self) { + trace!("irq: start"); + let mut more_work = true; + while more_work { + more_work = false; + match self.rx_state { + RxState::Idle => { + trace!(" irq_rx: in state idle"); + + if self.inner.events_rxdrdy.read().bits() != 0 { + trace!(" irq_rx: rxdrdy?????"); + self.inner.events_rxdrdy.reset(); + } + + if self.inner.events_endrx.read().bits() != 0 { + panic!("unexpected endrx"); + } + + let buf = self.rx.push_buf(); + if buf.len() != 0 { + trace!(" irq_rx: starting {:?}", buf.len()); + self.rx_state = RxState::Receiving; + + // Set up the DMA read + self.inner.rxd.ptr.write(|w| + // The PTR field is a full 32 bits wide and accepts the full range + // of values. + unsafe { w.ptr().bits(buf.as_ptr() as u32) }); + self.inner.rxd.maxcnt.write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to `u8` is also fine. + // + // The MAXCNT field is at least 8 bits wide and accepts the full + // range of values. + unsafe { w.maxcnt().bits(buf.len() as _) }); + trace!(" irq_rx: buf {:?} {:?}", buf.as_ptr() as u32, buf.len()); + + // Enable RXRDY interrupt. + self.inner.events_rxdrdy.reset(); + self.inner.intenset.write(|w| w.rxdrdy().set()); + + // Start UARTE Receive transaction + self.inner.tasks_startrx.write(|w| + // `1` is a valid value to write to task registers. + unsafe { w.bits(1) }); + } + } + RxState::Receiving => { + trace!(" irq_rx: in state receiving"); + if self.inner.events_rxdrdy.read().bits() != 0 { + trace!(" irq_rx: rxdrdy"); + + // Disable the RXRDY event interrupt + // RXRDY is triggered for every byte, but we only care about whether we have + // some bytes or not. So as soon as we have at least one, disable it, to avoid + // wasting CPU cycles in interrupts. + self.inner.intenclr.write(|w| w.rxdrdy().clear()); + + self.inner.events_rxdrdy.reset(); + + self.rx_waker.wake(); + self.rx_state = RxState::ReceivingReady; + more_work = true; // in case we also have endrx pending + } + } + RxState::ReceivingReady | RxState::Stopping => { + trace!(" irq_rx: in state ReceivingReady"); + + if self.inner.events_rxdrdy.read().bits() != 0 { + trace!(" irq_rx: rxdrdy"); + self.inner.events_rxdrdy.reset(); + } + + if self.inner.events_endrx.read().bits() != 0 { + let n: usize = self.inner.rxd.amount.read().amount().bits() as usize; + trace!(" irq_rx: endrx {:?}", n); + self.rx.push(n); + + self.inner.events_endrx.reset(); + + self.rx_waker.wake(); + self.rx_state = RxState::Idle; + more_work = true; // start another rx if possible + } + } + } + } + + more_work = true; + while more_work { + more_work = false; + match self.tx_state { + TxState::Idle => { + trace!(" irq_tx: in state Idle"); + let buf = self.tx.pop_buf(); + if buf.len() != 0 { + trace!(" irq_tx: starting {:?}", buf.len()); + self.tx_state = TxState::Transmitting(buf.len()); + + // Set up the DMA write + self.inner.txd.ptr.write(|w| + // The PTR field is a full 32 bits wide and accepts the full range + // of values. + unsafe { w.ptr().bits(buf.as_ptr() as u32) }); + self.inner.txd.maxcnt.write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to `u8` is also fine. + // + // The MAXCNT field is 8 bits wide and accepts the full range of + // values. + unsafe { w.maxcnt().bits(buf.len() as _) }); + + // Start UARTE Transmit transaction + self.inner.tasks_starttx.write(|w| + // `1` is a valid value to write to task registers. + unsafe { w.bits(1) }); + } + } + TxState::Transmitting(n) => { + trace!(" irq_tx: in state Transmitting"); + if self.inner.events_endtx.read().bits() != 0 { + self.inner.events_endtx.reset(); + + trace!(" irq_tx: endtx {:?}", n); + self.tx.pop(n); + self.tx_waker.wake(); + self.tx_state = TxState::Idle; + more_work = true; // start another tx if possible + } + } + } + } + trace!("irq: end"); + } +} + +pub struct Pins { + pub rxd: GpioPin>, + pub txd: GpioPin>, + pub cts: Option>>, + pub rts: Option>>, +} + +mod private { + use nrf52840_pac::{UARTE0, UARTE1}; + pub trait Sealed {} + + impl Sealed for UARTE0 {} + impl Sealed for UARTE1 {} +} + +pub trait Instance: Deref + Sized + private::Sealed { + fn interrupt() -> Interrupt; + + #[doc(hidden)] + fn get_state(_cs: &CriticalSection) -> *mut UarteState; + + #[doc(hidden)] + fn set_state(_cs: &CriticalSection, state: *mut UarteState); +} + +#[interrupt] +unsafe fn UARTE0_UART0() { + interrupt::free(|cs| UARTE0::get_state(cs).as_mut().unwrap().on_interrupt()); +} + +#[interrupt] +unsafe fn UARTE1() { + interrupt::free(|cs| UARTE1::get_state(cs).as_mut().unwrap().on_interrupt()); +} + +static mut UARTE0_STATE: *mut UarteState = ptr::null_mut(); +static mut UARTE1_STATE: *mut UarteState = ptr::null_mut(); + +impl Instance for UARTE0 { + fn interrupt() -> Interrupt { + Interrupt::UARTE0_UART0 + } + + fn get_state(_cs: &CriticalSection) -> *mut UarteState { + unsafe { UARTE0_STATE } // Safe because of CriticalSection + } + fn set_state(_cs: &CriticalSection, state: *mut UarteState) { + unsafe { UARTE0_STATE = state } // Safe because of CriticalSection + } +} + +impl Instance for UARTE1 { + fn interrupt() -> Interrupt { + Interrupt::UARTE1 + } + + fn get_state(_cs: &CriticalSection) -> *mut UarteState { + unsafe { UARTE1_STATE } // Safe because of CriticalSection + } + fn set_state(_cs: &CriticalSection, state: *mut UarteState) { + unsafe { UARTE1_STATE = state } // Safe because of CriticalSection + } +} diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml new file mode 100644 index 000000000..f621015ab --- /dev/null +++ b/embassy/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "embassy" +version = "0.1.0" +authors = ["Dario Nieuwenhuis "] +edition = "2018" + +[features] +std = [] + +[dependencies] +defmt = "0.1.0" +cortex-m = "0.6.3" +futures = { version = "0.3.5", default-features = false, features = [ "async-await" ] } +pin-project = { version = "0.4.23", default-features = false } diff --git a/embassy/src/flash.rs b/embassy/src/flash.rs new file mode 100644 index 000000000..bf6d59804 --- /dev/null +++ b/embassy/src/flash.rs @@ -0,0 +1,51 @@ + +use core::future::Future; + +#[derive(defmt::Format, Copy, Clone, Debug, Eq, PartialEq)] +pub enum Error { + Failed, + AddressMisaligned, + BufferMisaligned, + + _NonExhaustive, +} + +pub trait Flash { + type ReadFuture<'a>: Future>; + type WriteFuture<'a>: Future>; + type ErasePageFuture<'a>: Future>; + + /// Reads data from the flash device. + /// + /// address must be a multiple of self.read_size(). + /// buf.len() must be a multiple of self.read_size(). + fn read<'a>(&'a mut self, address: usize, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; + + /// Writes data to the flash device. + /// + /// address must be a multiple of self.write_size(). + /// buf.len() must be a multiple of self.write_size(). + fn write<'a>(&'a mut self, address: usize, buf: &'a [u8]) -> Self::WriteFuture<'a>; + + /// Erases a single page from the flash device. + /// + /// address must be a multiple of self.erase_size(). + fn erase<'a>(&'a mut self, address: usize) -> Self::ErasePageFuture<'a>; + + /// Returns the total size, in bytes. + /// This is not guaranteed to be a power of 2. + fn size(&self) -> usize; + + /// Returns the read size in bytes. + /// This is guaranteed to be a power of 2. + fn read_size(&self) -> usize; + + /// Returns the write size in bytes. + /// This is guaranteed to be a power of 2. + fn write_size(&self) -> usize; + + /// Returns the erase size in bytes. + /// This is guaranteed to be a power of 2. + fn erase_size(&self) -> usize; +} + diff --git a/embassy/src/io/error.rs b/embassy/src/io/error.rs new file mode 100644 index 000000000..2f1d4810e --- /dev/null +++ b/embassy/src/io/error.rs @@ -0,0 +1,133 @@ +#[cfg(feature = "std")] +use core::convert::From; +#[cfg(feature = "std")] +use futures::io; + +/// Categories of errors that can occur. +/// +/// This list is intended to grow over time and it is not recommended to +/// exhaustively match against it. +#[derive(defmt::Format, Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + /// An entity was not found, often a file. + NotFound, + /// The operation lacked the necessary privileges to complete. + PermissionDenied, + /// The connection was refused by the remote server. + ConnectionRefused, + /// The connection was reset by the remote server. + ConnectionReset, + /// The connection was aborted (terminated) by the remote server. + ConnectionAborted, + /// The network operation failed because it was not connected yet. + NotConnected, + /// A socket address could not be bound because the address is already in + /// use elsewhere. + AddrInUse, + /// A nonexistent interface was requested or the requested address was not + /// local. + AddrNotAvailable, + /// The operation failed because a pipe was closed. + BrokenPipe, + /// An entity already exists, often a file. + AlreadyExists, + /// The operation needs to block to complete, but the blocking operation was + /// requested to not occur. + WouldBlock, + /// A parameter was incorrect. + InvalidInput, + /// Data not valid for the operation were encountered. + /// + /// Unlike [`InvalidInput`], this typically means that the operation + /// parameters were valid, however the error was caused by malformed + /// input data. + /// + /// For example, a function that reads a file into a string will error with + /// `InvalidData` if the file's contents are not valid UTF-8. + /// + /// [`InvalidInput`]: #variant.InvalidInput + InvalidData, + /// The I/O operation's timeout expired, causing it to be canceled. + TimedOut, + /// An error returned when an operation could not be completed because a + /// call to [`write`] returned [`Ok(0)`]. + /// + /// This typically means that an operation could only succeed if it wrote a + /// particular number of bytes but only a smaller number of bytes could be + /// written. + /// + /// [`write`]: ../../std/io/trait.Write.html#tymethod.write + /// [`Ok(0)`]: ../../std/io/type.Result.html + WriteZero, + /// This operation was interrupted. + /// + /// Interrupted operations can typically be retried. + Interrupted, + + /// An error returned when an operation could not be completed because an + /// "end of file" was reached prematurely. + /// + /// This typically means that an operation could only succeed if it read a + /// particular number of bytes but only a smaller number of bytes could be + /// read. + UnexpectedEof, + + /// An operation would have read more data if the given buffer was large. + /// + /// This typically means that the buffer has been filled with the first N bytes + /// of the read data. + Truncated, + + /// Any I/O error not part of this list. + Other, +} + +pub type Result = core::result::Result; + +#[cfg(feature = "std")] +impl From for Error { + fn from(err: io::Error) -> Error { + match err.kind() { + io::ErrorKind::NotFound => Error::NotFound, + io::ErrorKind::PermissionDenied => Error::PermissionDenied, + io::ErrorKind::ConnectionRefused => Error::ConnectionRefused, + io::ErrorKind::ConnectionReset => Error::ConnectionReset, + io::ErrorKind::ConnectionAborted => Error::ConnectionAborted, + io::ErrorKind::NotConnected => Error::NotConnected, + io::ErrorKind::AddrInUse => Error::AddrInUse, + io::ErrorKind::AddrNotAvailable => Error::AddrNotAvailable, + io::ErrorKind::BrokenPipe => Error::BrokenPipe, + io::ErrorKind::AlreadyExists => Error::AlreadyExists, + io::ErrorKind::WouldBlock => Error::WouldBlock, + io::ErrorKind::InvalidInput => Error::InvalidInput, + io::ErrorKind::InvalidData => Error::InvalidData, + io::ErrorKind::TimedOut => Error::TimedOut, + io::ErrorKind::WriteZero => Error::WriteZero, + io::ErrorKind::Interrupted => Error::Interrupted, + io::ErrorKind::UnexpectedEof => Error::UnexpectedEof, + _ => Error::Other, + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +/* +impl From for Error { + fn from(err: smoltcp::Error) -> Error { + match err { + smoltcp::Error::Exhausted => Error::Exhausted, + smoltcp::Error::Illegal => Error::Illegal, + smoltcp::Error::Unaddressable => Error::Unaddressable, + smoltcp::Error::Truncated => Error::Truncated, + smoltcp::Error::Checksum => Error::Checksum, + smoltcp::Error::Unrecognized => Error::Unrecognized, + smoltcp::Error::Fragmented => Error::Fragmented, + smoltcp::Error::Malformed => Error::Malformed, + smoltcp::Error::Dropped => Error::Dropped, + _ => Error::Other, + } + } +} +*/ diff --git a/embassy/src/io/mod.rs b/embassy/src/io/mod.rs new file mode 100644 index 000000000..8445f6e80 --- /dev/null +++ b/embassy/src/io/mod.rs @@ -0,0 +1,7 @@ +mod error; +mod traits; +mod util; + +pub use self::error::*; +pub use self::traits::*; +pub use self::util::*; diff --git a/embassy/src/io/traits.rs b/embassy/src/io/traits.rs new file mode 100644 index 000000000..f1f91a46c --- /dev/null +++ b/embassy/src/io/traits.rs @@ -0,0 +1,197 @@ + +use core::ops::DerefMut; +use core::pin::Pin; +use core::task::{Context, Poll}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "std")] +use futures::io as std_io; + +use super::error::Result; + +/// Read bytes asynchronously. +/// +/// This trait is analogous to the `std::io::BufRead` trait, but integrates +/// with the asynchronous task system. In particular, the `poll_fill_buf` +/// method, unlike `BufRead::fill_buf`, will automatically queue the current task +/// for wakeup and return if data is not yet available, rather than blocking +/// the calling thread. +pub trait AsyncBufRead { + /// Attempt to return the contents of the internal buffer, filling it with more data + /// from the inner reader if it is empty. + /// + /// On success, returns `Poll::Ready(Ok(buf))`. + /// + /// If no data is available for reading, the method returns + /// `Poll::Pending` and arranges for the current task (via + /// `cx.waker().wake_by_ref()`) to receive a notification when the object becomes + /// readable or is closed. + /// + /// This function is a lower-level call. It needs to be paired with the + /// [`consume`] method to function properly. When calling this + /// method, none of the contents will be "read" in the sense that later + /// calling [`poll_read`] may return the same contents. As such, [`consume`] must + /// be called with the number of bytes that are consumed from this buffer to + /// ensure that the bytes are never returned twice. + /// + /// [`poll_read`]: AsyncBufRead::poll_read + /// [`consume`]: AsyncBufRead::consume + /// + /// An empty buffer returned indicates that the stream has reached EOF. + /// + /// # Implementation + /// + /// This function may not return errors of kind `WouldBlock` or + /// `Interrupted`. Implementations must convert `WouldBlock` into + /// `Poll::Pending` and either internally retry or convert + /// `Interrupted` into another error kind. + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + + /// Tells this buffer that `amt` bytes have been consumed from the buffer, + /// so they should no longer be returned in calls to [`poll_read`]. + /// + /// This function is a lower-level call. It needs to be paired with the + /// [`poll_fill_buf`] method to function properly. This function does + /// not perform any I/O, it simply informs this object that some amount of + /// its buffer, returned from [`poll_fill_buf`], has been consumed and should + /// no longer be returned. As such, this function may do odd things if + /// [`poll_fill_buf`] isn't called before calling it. + /// + /// The `amt` must be `<=` the number of bytes in the buffer returned by + /// [`poll_fill_buf`]. + /// + /// [`poll_read`]: AsyncBufRead::poll_read + /// [`poll_fill_buf`]: AsyncBufRead::poll_fill_buf + fn consume(self: Pin<&mut Self>, amt: usize); +} + +/// Write bytes asynchronously. +/// +/// This trait is analogous to the `core::io::Write` trait, but integrates +/// with the asynchronous task system. In particular, the `poll_write` +/// method, unlike `Write::write`, will automatically queue the current task +/// for wakeup and return if the writer cannot take more data, rather than blocking +/// the calling thread. +pub trait AsyncWrite { + /// Attempt to write bytes from `buf` into the object. + /// + /// On success, returns `Poll::Ready(Ok(num_bytes_written))`. + /// + /// If the object is not ready for writing, the method returns + /// `Poll::Pending` and arranges for the current task (via + /// `cx.waker().wake_by_ref()`) to receive a notification when the object becomes + /// writable or is closed. + /// + /// # Implementation + /// + /// This function may not return errors of kind `WouldBlock` or + /// `Interrupted`. Implementations must convert `WouldBlock` into + /// `Poll::Pending` and either internally retry or convert + /// `Interrupted` into another error kind. + /// + /// `poll_write` must try to make progress by flushing the underlying object if + /// that is the only way the underlying object can become writable again. + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll>; +} + +macro_rules! defer_async_read { + () => { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_fill_buf(cx) + } + + fn consume(mut self: Pin<&mut Self>, amt: usize) { + Pin::new(&mut **self).consume(amt) + } + }; +} + +#[cfg(feature = "alloc")] +impl AsyncBufRead for Box { + defer_async_read!(); +} + +impl AsyncBufRead for &mut T { + defer_async_read!(); +} + +impl

AsyncBufRead for Pin

+where + P: DerefMut + Unpin, + P::Target: AsyncBufRead, +{ + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.get_mut().as_mut().poll_fill_buf(cx) + } + + fn consume(self: Pin<&mut Self>, amt: usize) { + self.get_mut().as_mut().consume(amt) + } +} + +macro_rules! deref_async_write { + () => { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut **self).poll_write(cx, buf) + } + }; +} + +#[cfg(feature = "alloc")] +impl AsyncWrite for Box { + deref_async_write!(); +} + +impl AsyncWrite for &mut T { + deref_async_write!(); +} + +impl

AsyncWrite for Pin

+where + P: DerefMut + Unpin, + P::Target: AsyncWrite, +{ + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + self.get_mut().as_mut().poll_write(cx, buf) + } +} + +#[cfg(feature = "std")] +pub struct FromStdIo(T); + +#[cfg(feature = "std")] +impl FromStdIo { + pub fn new(inner: T) -> Self { + Self(inner) + } +} + +#[cfg(feature = "std")] +impl AsyncBufRead for FromStdIo { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let Self(inner) = unsafe { self.get_unchecked_mut() }; + unsafe { Pin::new_unchecked(inner) } + .poll_fill_buf(cx) + .map_err(|e| e.into()) + } + fn consume(self: Pin<&mut Self>, amt: usize) { + let Self(inner) = unsafe { self.get_unchecked_mut() }; + unsafe { Pin::new_unchecked(inner) }.consume(amt) + } +} + +#[cfg(feature = "std")] +impl AsyncWrite for FromStdIo { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + let Self(inner) = unsafe { self.get_unchecked_mut() }; + unsafe { Pin::new_unchecked(inner) } + .poll_write(cx, buf) + .map_err(|e| e.into()) + } +} diff --git a/embassy/src/io/util/copy_buf.rs b/embassy/src/io/util/copy_buf.rs new file mode 100644 index 000000000..c037f3b02 --- /dev/null +++ b/embassy/src/io/util/copy_buf.rs @@ -0,0 +1,80 @@ +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; +use futures::ready; +use pin_project::pin_project; + +use crate::io::{AsyncBufRead, AsyncWrite, Error, Result}; + +/// Creates a future which copies all the bytes from one object to another. +/// +/// The returned future will copy all the bytes read from this `AsyncBufRead` into the +/// `writer` specified. This future will only complete once the `reader` has hit +/// EOF and all bytes have been written to and flushed from the `writer` +/// provided. +/// +/// On success the number of bytes is returned. +/// +/// # Examples +/// +/// ``` +/// # futures::executor::block_on(async { +/// use futures::io::{self, AsyncWriteExt, Cursor}; +/// +/// let reader = Cursor::new([1, 2, 3, 4]); +/// let mut writer = Cursor::new(vec![0u8; 5]); +/// +/// let bytes = io::copy_buf(reader, &mut writer).await?; +/// writer.close().await?; +/// +/// assert_eq!(bytes, 4); +/// assert_eq!(writer.into_inner(), [1, 2, 3, 4, 0]); +/// # Ok::<(), Box>(()) }).unwrap(); +/// ``` +pub fn copy_buf(reader: R, writer: &mut W) -> CopyBuf<'_, R, W> +where + R: AsyncBufRead, + W: AsyncWrite + Unpin + ?Sized, +{ + CopyBuf { + reader, + writer, + amt: 0, + } +} + +/// Future for the [`copy_buf()`] function. +#[pin_project] +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct CopyBuf<'a, R, W: ?Sized> { + #[pin] + reader: R, + writer: &'a mut W, + amt: usize, +} + +impl Future for CopyBuf<'_, R, W> +where + R: AsyncBufRead, + W: AsyncWrite + Unpin + ?Sized, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + loop { + let buffer = ready!(this.reader.as_mut().poll_fill_buf(cx))?; + if buffer.is_empty() { + return Poll::Ready(Ok(*this.amt)); + } + + let i = ready!(Pin::new(&mut this.writer).poll_write(cx, buffer))?; + if i == 0 { + return Poll::Ready(Err(Error::WriteZero.into())); + } + *this.amt += i; + this.reader.as_mut().consume(i); + } + } +} diff --git a/embassy/src/io/util/mod.rs b/embassy/src/io/util/mod.rs new file mode 100644 index 000000000..c95a23f0a --- /dev/null +++ b/embassy/src/io/util/mod.rs @@ -0,0 +1,145 @@ +use core::cmp::min; +use core::pin::Pin; +use core::task::{Context, Poll}; +use futures::ready; + +mod read; +pub use self::read::Read; + +mod read_buf; +pub use self::read_buf::ReadBuf; + +mod read_byte; +pub use self::read_byte::ReadByte; + +mod read_exact; +pub use self::read_exact::ReadExact; + +mod read_while; +pub use self::read_while::ReadWhile; + +mod read_to_end; +pub use self::read_to_end::ReadToEnd; + +mod skip_while; +pub use self::skip_while::SkipWhile; + +mod write; +pub use self::write::Write; + +mod write_all; +pub use self::write_all::WriteAll; + +mod write_byte; +pub use self::write_byte::WriteByte; + +#[cfg(feature = "alloc")] +mod split; +#[cfg(feature = "alloc")] +pub use self::split::{split, ReadHalf, WriteHalf}; + +mod copy_buf; +pub use self::copy_buf::{copy_buf, CopyBuf}; + +use super::error::Result; +use super::traits::{AsyncBufRead, AsyncWrite}; + +pub trait AsyncBufReadExt: AsyncBufRead { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> + where + Self: Unpin, + { + let mut this = &mut *self; + let rbuf = ready!(Pin::new(&mut this).poll_fill_buf(cx))?; + let n = min(buf.len(), rbuf.len()); + buf[..n].copy_from_slice(&rbuf[..n]); + Pin::new(&mut this).consume(n); + Poll::Ready(Ok(n)) + } + + fn read_while<'a, F: Fn(u8) -> bool>( + &'a mut self, + buf: &'a mut [u8], + f: F, + ) -> ReadWhile<'a, Self, F> + where + Self: Unpin, + { + ReadWhile::new(self, f, buf) + } + + fn skip_while<'a, F: Fn(u8) -> bool>(&'a mut self, f: F) -> SkipWhile<'a, Self, F> + where + Self: Unpin, + { + SkipWhile::new(self, f) + } + + fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Read<'a, Self> + where + Self: Unpin, + { + Read::new(self, buf) + } + + fn read_buf<'a>(&'a mut self) -> ReadBuf<'a, Self> + where + Self: Unpin, + { + ReadBuf::new(self) + } + + fn read_byte<'a>(&'a mut self) -> ReadByte<'a, Self> + where + Self: Unpin, + { + ReadByte::new(self) + } + + fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadExact<'a, Self> + where + Self: Unpin, + { + ReadExact::new(self, buf) + } + + fn read_to_end<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadToEnd<'a, Self> + where + Self: Unpin, + { + ReadToEnd::new(self, buf) + } +} + +impl AsyncBufReadExt for R {} + +pub async fn read_line(r: &mut R, buf: &mut [u8]) -> Result { + r.skip_while(|b| b == b'\r' || b == b'\n').await?; + let n = r.read_while(buf, |b| b != b'\r' && b != b'\n').await?; + r.skip_while(|b| b == b'\r').await?; + //assert_eq!(b'\n', r.read_byte().await?); + r.read_byte().await?; + Ok(n) +} + +pub trait AsyncWriteExt: AsyncWrite { + fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> WriteAll<'a, Self> + where + Self: Unpin, + { + WriteAll::new(self, buf) + } + + fn write_byte<'a>(&'a mut self, byte: u8) -> WriteByte<'a, Self> + where + Self: Unpin, + { + WriteByte::new(self, byte) + } +} + +impl AsyncWriteExt for R {} diff --git a/embassy/src/io/util/read.rs b/embassy/src/io/util/read.rs new file mode 100644 index 000000000..31aaa0da5 --- /dev/null +++ b/embassy/src/io/util/read.rs @@ -0,0 +1,39 @@ +use super::super::error::{Result}; +use super::super::traits::AsyncBufRead; + +use core::cmp::min; + +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +/// Future for the [`read_exact`](super::AsyncBufReadExt::read_exact) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Read<'a, R: ?Sized> { + reader: &'a mut R, + buf: &'a mut [u8], +} + +impl Unpin for Read<'_, R> {} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin> Read<'a, R> { + pub(super) fn new(reader: &'a mut R, buf: &'a mut [u8]) -> Self { + Read { reader, buf } + } +} + +impl Future for Read<'_, R> { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = &mut *self; + let buf = ready!(Pin::new(&mut this.reader).poll_fill_buf(cx))?; + + let n = min(this.buf.len(), buf.len()); + this.buf[..n].copy_from_slice(&buf[..n]); + Pin::new(&mut this.reader).consume(n); + Poll::Ready(Ok(n)) + } +} diff --git a/embassy/src/io/util/read_buf.rs b/embassy/src/io/util/read_buf.rs new file mode 100644 index 000000000..7489eac26 --- /dev/null +++ b/embassy/src/io/util/read_buf.rs @@ -0,0 +1,34 @@ +use super::super::error::{Result}; +use super::super::traits::AsyncBufRead; + +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +pub struct ReadBuf<'a, R: ?Sized> { + reader: Option<&'a mut R>, +} + +impl Unpin for ReadBuf<'_, R> {} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin> ReadBuf<'a, R> { + pub(super) fn new(reader: &'a mut R) -> Self { + ReadBuf { + reader: Some(reader), + } + } +} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin> Future for ReadBuf<'a, R> { + type Output = Result<&'a [u8]>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = &mut *self; + + let buf = ready!(Pin::new(this.reader.as_mut().unwrap()).poll_fill_buf(cx))?; + let buf: &'a [u8] = unsafe { core::mem::transmute(buf) }; + this.reader = None; + Poll::Ready(Ok(buf)) + } +} diff --git a/embassy/src/io/util/read_byte.rs b/embassy/src/io/util/read_byte.rs new file mode 100644 index 000000000..7b7865ba9 --- /dev/null +++ b/embassy/src/io/util/read_byte.rs @@ -0,0 +1,36 @@ +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +use super::super::error::{Error, Result}; +use super::super::traits::AsyncBufRead; + +pub struct ReadByte<'a, R: ?Sized> { + reader: &'a mut R, +} + +impl Unpin for ReadByte<'_, R> {} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin> ReadByte<'a, R> { + pub(super) fn new(reader: &'a mut R) -> Self { + Self { reader } + } +} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin> Future for ReadByte<'a, R> { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Self { reader } = &mut *self; + let mut reader = Pin::new(reader); + let rbuf = ready!(reader.as_mut().poll_fill_buf(cx))?; + if rbuf.len() == 0 { + return Poll::Ready(Err(Error::UnexpectedEof)); + } + + let r = rbuf[0]; + reader.as_mut().consume(1); + Poll::Ready(Ok(r)) + } +} diff --git a/embassy/src/io/util/read_exact.rs b/embassy/src/io/util/read_exact.rs new file mode 100644 index 000000000..b7f7355ef --- /dev/null +++ b/embassy/src/io/util/read_exact.rs @@ -0,0 +1,48 @@ +use super::super::error::{Error, Result}; +use super::super::traits::AsyncBufRead; + +use core::cmp::min; +use core::mem; +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +/// Future for the [`read_exact`](super::AsyncBufReadExt::read_exact) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReadExact<'a, R: ?Sized> { + reader: &'a mut R, + buf: &'a mut [u8], +} + +impl Unpin for ReadExact<'_, R> {} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin> ReadExact<'a, R> { + pub(super) fn new(reader: &'a mut R, buf: &'a mut [u8]) -> Self { + ReadExact { reader, buf } + } +} + +impl Future for ReadExact<'_, R> { + type Output = Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = &mut *self; + while !this.buf.is_empty() { + let buf = ready!(Pin::new(&mut this.reader).poll_fill_buf(cx))?; + if buf.len() == 0 { + return Poll::Ready(Err(Error::UnexpectedEof)); + } + + let n = min(this.buf.len(), buf.len()); + this.buf[..n].copy_from_slice(&buf[..n]); + Pin::new(&mut this.reader).consume(n); + { + let (_, rest) = mem::replace(&mut this.buf, &mut []).split_at_mut(n); + this.buf = rest; + } + } + Poll::Ready(Ok(())) + } +} diff --git a/embassy/src/io/util/read_to_end.rs b/embassy/src/io/util/read_to_end.rs new file mode 100644 index 000000000..2da6c74d8 --- /dev/null +++ b/embassy/src/io/util/read_to_end.rs @@ -0,0 +1,48 @@ +use core::cmp::min; +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +use super::super::error::{Error, Result}; +use super::super::traits::AsyncBufRead; + +pub struct ReadToEnd<'a, R: ?Sized> { + reader: &'a mut R, + buf: &'a mut [u8], + n: usize, +} + +impl Unpin for ReadToEnd<'_, R> {} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin> ReadToEnd<'a, R> { + pub(super) fn new(reader: &'a mut R, buf: &'a mut [u8]) -> Self { + Self { reader, buf, n: 0 } + } +} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin> Future for ReadToEnd<'a, R> { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Self { reader, buf, n } = &mut *self; + let mut reader = Pin::new(reader); + loop { + let rbuf = ready!(reader.as_mut().poll_fill_buf(cx))?; + if rbuf.len() == 0 { + return Poll::Ready(Ok(*n)); + } + + if *n == buf.len() { + return Poll::Ready(Err(Error::Truncated)); + } + + // truncate data if it doesn't fit in buf + let p = min(rbuf.len(), buf.len() - *n); + buf[*n..*n + p].copy_from_slice(&rbuf[..p]); + *n += p; + + reader.as_mut().consume(p); + } + } +} diff --git a/embassy/src/io/util/read_while.rs b/embassy/src/io/util/read_while.rs new file mode 100644 index 000000000..ab46cee38 --- /dev/null +++ b/embassy/src/io/util/read_while.rs @@ -0,0 +1,61 @@ +use core::cmp::min; +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +use super::super::error::{Error, Result}; +use super::super::traits::AsyncBufRead; + +pub struct ReadWhile<'a, R: ?Sized, F> { + reader: &'a mut R, + buf: &'a mut [u8], + n: usize, + f: F, +} + +impl Unpin for ReadWhile<'_, R, F> {} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin, F: Fn(u8) -> bool> ReadWhile<'a, R, F> { + pub(super) fn new(reader: &'a mut R, f: F, buf: &'a mut [u8]) -> Self { + Self { + reader, + f, + buf, + n: 0, + } + } +} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin, F: Fn(u8) -> bool> Future for ReadWhile<'a, R, F> { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Self { reader, f, buf, n } = &mut *self; + let mut reader = Pin::new(reader); + loop { + let rbuf = ready!(reader.as_mut().poll_fill_buf(cx))?; + if rbuf.len() == 0 { + return Poll::Ready(Err(Error::UnexpectedEof)); + } + + let (p, done) = match rbuf.iter().position(|&b| !f(b)) { + Some(p) => (p, true), + None => (rbuf.len(), false), + }; + + // truncate data if it doesn't fit in buf + let p2 = min(p, buf.len() - *n); + buf[*n..*n + p2].copy_from_slice(&rbuf[..p2]); + *n += p2; + + // consume it all, even if it doesn't fit. + // Otherwise we can deadlock because we never read to the ending char + reader.as_mut().consume(p); + + if done { + return Poll::Ready(Ok(*n)); + } + } + } +} diff --git a/embassy/src/io/util/skip_while.rs b/embassy/src/io/util/skip_while.rs new file mode 100644 index 000000000..8c81ad209 --- /dev/null +++ b/embassy/src/io/util/skip_while.rs @@ -0,0 +1,45 @@ +use core::iter::Iterator; +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +use super::super::error::{Error, Result}; +use super::super::traits::AsyncBufRead; + +pub struct SkipWhile<'a, R: ?Sized, F> { + reader: &'a mut R, + f: F, +} + +impl Unpin for SkipWhile<'_, R, F> {} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin, F: Fn(u8) -> bool> SkipWhile<'a, R, F> { + pub(super) fn new(reader: &'a mut R, f: F) -> Self { + Self { reader, f } + } +} + +impl<'a, R: AsyncBufRead + ?Sized + Unpin, F: Fn(u8) -> bool> Future for SkipWhile<'a, R, F> { + type Output = Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Self { reader, f } = &mut *self; + let mut reader = Pin::new(reader); + loop { + let buf = ready!(reader.as_mut().poll_fill_buf(cx))?; + if buf.len() == 0 { + return Poll::Ready(Err(Error::UnexpectedEof)); + } + + let (p, done) = match buf.iter().position(|b| !f(*b)) { + Some(p) => (p, true), + None => (buf.len(), false), + }; + reader.as_mut().consume(p); + if done { + return Poll::Ready(Ok(())); + } + } + } +} diff --git a/embassy/src/io/util/split.rs b/embassy/src/io/util/split.rs new file mode 100644 index 000000000..0cebb5cbd --- /dev/null +++ b/embassy/src/io/util/split.rs @@ -0,0 +1,40 @@ +use alloc::rc::Rc; +use core::cell::UnsafeCell; +use core::pin::Pin; +use futures::task::{Context, Poll}; + +use super::super::error::Result; +use super::super::traits::{AsyncBufRead, AsyncWrite}; + +/// The readable half of an object returned from `AsyncBufRead::split`. +#[derive(Debug)] +pub struct ReadHalf { + handle: Rc>, +} + +/// The writable half of an object returned from `AsyncBufRead::split`. +#[derive(Debug)] +pub struct WriteHalf { + handle: Rc>, +} + +impl AsyncBufRead for ReadHalf { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(unsafe { &mut *self.handle.get() }).poll_fill_buf(cx) + } + + fn consume(self: Pin<&mut Self>, amt: usize) { + Pin::new(unsafe { &mut *self.handle.get() }).consume(amt) + } +} + +impl AsyncWrite for WriteHalf { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + Pin::new(unsafe { &mut *self.handle.get() }).poll_write(cx, buf) + } +} + +pub fn split(t: T) -> (ReadHalf, WriteHalf) { + let c = Rc::new(UnsafeCell::new(t)); + (ReadHalf { handle: c.clone() }, WriteHalf { handle: c }) +} diff --git a/embassy/src/io/util/write.rs b/embassy/src/io/util/write.rs new file mode 100644 index 000000000..403cd59fe --- /dev/null +++ b/embassy/src/io/util/write.rs @@ -0,0 +1,33 @@ +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +use super::super::error::Result; +use super::super::traits::AsyncWrite; + +/// Future for the [`write_all`](super::AsyncWriteExt::write_all) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Write<'a, W: ?Sized> { + writer: &'a mut W, + buf: &'a [u8], +} + +impl Unpin for Write<'_, W> {} + +impl<'a, W: AsyncWrite + ?Sized + Unpin> Write<'a, W> { + pub(super) fn new(writer: &'a mut W, buf: &'a [u8]) -> Self { + Write { writer, buf } + } +} + +impl Future for Write<'_, W> { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = &mut *self; + let n = ready!(Pin::new(&mut this.writer).poll_write(cx, this.buf))?; + Poll::Ready(Ok(n)) + } +} diff --git a/embassy/src/io/util/write_all.rs b/embassy/src/io/util/write_all.rs new file mode 100644 index 000000000..76b6ec092 --- /dev/null +++ b/embassy/src/io/util/write_all.rs @@ -0,0 +1,44 @@ +use core::mem; +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +use super::super::error::Result; +use super::super::traits::AsyncWrite; + +/// Future for the [`write_all`](super::AsyncWriteExt::write_all) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct WriteAll<'a, W: ?Sized> { + writer: &'a mut W, + buf: &'a [u8], +} + +impl Unpin for WriteAll<'_, W> {} + +impl<'a, W: AsyncWrite + ?Sized + Unpin> WriteAll<'a, W> { + pub(super) fn new(writer: &'a mut W, buf: &'a [u8]) -> Self { + WriteAll { writer, buf } + } +} + +impl Future for WriteAll<'_, W> { + type Output = Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = &mut *self; + while !this.buf.is_empty() { + let n = ready!(Pin::new(&mut this.writer).poll_write(cx, this.buf))?; + { + let (_, rest) = mem::replace(&mut this.buf, &[]).split_at(n); + this.buf = rest; + } + if n == 0 { + panic!(); + } + } + + Poll::Ready(Ok(())) + } +} diff --git a/embassy/src/io/util/write_byte.rs b/embassy/src/io/util/write_byte.rs new file mode 100644 index 000000000..659e427b1 --- /dev/null +++ b/embassy/src/io/util/write_byte.rs @@ -0,0 +1,39 @@ +use core::pin::Pin; +use futures::future::Future; +use futures::ready; +use futures::task::{Context, Poll}; + +use super::super::error::Result; +use super::super::traits::AsyncWrite; + +/// Future for the [`write_all`](super::AsyncWriteExt::write_all) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct WriteByte<'a, W: ?Sized> { + writer: &'a mut W, + byte: u8, +} + +impl Unpin for WriteByte<'_, W> {} + +impl<'a, W: AsyncWrite + ?Sized + Unpin> WriteByte<'a, W> { + pub(super) fn new(writer: &'a mut W, byte: u8) -> Self { + WriteByte { writer, byte } + } +} + +impl Future for WriteByte<'_, W> { + type Output = Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = &mut *self; + let buf = [this.byte; 1]; + let n = ready!(Pin::new(&mut this.writer).poll_write(cx, &buf))?; + if n == 0 { + panic!(); + } + assert!(n == 1); + + Poll::Ready(Ok(())) + } +} diff --git a/embassy/src/lib.rs b/embassy/src/lib.rs new file mode 100644 index 000000000..45716c6cf --- /dev/null +++ b/embassy/src/lib.rs @@ -0,0 +1,8 @@ +#![no_std] +#![feature(slice_fill)] +#![feature(generic_associated_types)] +#![feature(const_fn)] + +pub mod flash; +pub mod util; +pub mod io; diff --git a/embassy/src/util/drop_bomb.rs b/embassy/src/util/drop_bomb.rs new file mode 100644 index 000000000..2a995a826 --- /dev/null +++ b/embassy/src/util/drop_bomb.rs @@ -0,0 +1,21 @@ +use core::mem; + +pub struct DropBomb { + _private: (), +} + +impl DropBomb { + pub fn new() -> Self { + Self { _private: () } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for DropBomb { + fn drop(&mut self) { + depanic!("boom") + } +} diff --git a/embassy/src/util/macros.rs b/embassy/src/util/macros.rs new file mode 100644 index 000000000..69987e42c --- /dev/null +++ b/embassy/src/util/macros.rs @@ -0,0 +1,32 @@ +#![macro_use] + +macro_rules! depanic { + ($( $i:expr ),*) => { + { + defmt::error!($( $i ),*); + panic!(); + } + } +} + +macro_rules! deassert { + ($cond:expr) => { + deassert!($cond, "assertion failed"); + }; + ($cond:expr, $msg:literal) => { + { + if !$cond { + defmt::error!($msg); + panic!(); + } + } + }; + ($cond:expr, $msg:literal, $( $i:expr ),*) => { + { + if !$cond { + defmt::error!($msg, $( $i ),*); + panic!(); + } + } + }; +} diff --git a/embassy/src/util/mod.rs b/embassy/src/util/mod.rs new file mode 100644 index 000000000..3a0f11e6f --- /dev/null +++ b/embassy/src/util/mod.rs @@ -0,0 +1,70 @@ +#![macro_use] + +mod macros; + +mod signal; +pub use signal::*; +mod portal; +pub use portal::*; +mod waker_store; +pub use waker_store::*; +mod drop_bomb; +pub use drop_bomb::*; + +use defmt::{warn, error}; + +pub trait Dewrap { + /// dewrap = defmt unwrap + fn dewrap(self) -> T; + + /// dexpect = defmt expect + fn dexpect(self, msg: M) -> T; + + fn dewarn(self, msg: M) -> Self; +} + +impl Dewrap for Option { + fn dewrap(self) -> T { + match self { + Some(t) => t, + None => depanic!("unwrap failed: enum is none"), + } + } + + fn dexpect(self, msg: M) -> T { + match self { + Some(t) => t, + None => depanic!("unexpected None: {:?}", msg), + } + } + + fn dewarn(self, msg: M) -> Self { + if self.is_none() { + warn!("{:?} is none", msg); + } + self + } +} + +impl Dewrap for Result { + fn dewrap(self) -> T { + match self { + Ok(t) => t, + Err(e) => depanic!("unwrap failed: {:?}", e), + } + } + + fn dexpect(self, msg: M) -> T { + match self { + Ok(t) => t, + Err(e) => depanic!("unexpected error: {:?}: {:?}", msg, e), + } + } + + fn dewarn(self, msg: M) -> Self { + if let Err(e) = &self { + warn!("{:?} err: {:?}", msg, e); + } + self + } +} diff --git a/embassy/src/util/portal.rs b/embassy/src/util/portal.rs new file mode 100644 index 000000000..e01968c5f --- /dev/null +++ b/embassy/src/util/portal.rs @@ -0,0 +1,125 @@ +use core::cell::UnsafeCell; +use core::future::Future; +use core::mem; +use core::mem::MaybeUninit; + +use crate::util::*; + +/// Utility to call a closure across tasks. +pub struct Portal { + state: UnsafeCell>, +} + +enum State { + None, + Running, + Waiting(*mut dyn FnMut(T)), +} + +impl Portal { + pub const fn new() -> Self { + Self { + state: UnsafeCell::new(State::None), + } + } + + pub fn call(&self, val: T) { + unsafe { + match *self.state.get() { + State::None => {} + State::Running => depanic!("Portall::call() called reentrantly"), + State::Waiting(func) => (*func)(val), + } + } + } + + pub fn wait_once<'a, R, F>(&'a self, mut func: F) -> impl Future + 'a + where + F: FnMut(T) -> R + 'a, + { + async move { + let bomb = DropBomb::new(); + + let signal = Signal::new(); + let mut result: MaybeUninit = MaybeUninit::uninit(); + let mut call_func = |val: T| { + unsafe { + let state = &mut *self.state.get(); + *state = State::None; + result.as_mut_ptr().write(func(val)) + }; + signal.signal(()); + }; + + let func_ptr: *mut dyn FnMut(T) = &mut call_func as _; + let func_ptr: *mut dyn FnMut(T) = unsafe { mem::transmute(func_ptr) }; + + unsafe { + let state = &mut *self.state.get(); + match state { + State::None => {} + _ => depanic!("Multiple tasks waiting on same portal"), + } + *state = State::Waiting(func_ptr); + } + + signal.wait().await; + + bomb.defuse(); + + unsafe { result.assume_init() } + } + } + + pub fn wait_many<'a, R, F>(&'a self, mut func: F) -> impl Future + 'a + where + F: FnMut(T) -> Option + 'a, + { + async move { + let bomb = DropBomb::new(); + + let signal = Signal::new(); + let mut result: MaybeUninit = MaybeUninit::uninit(); + let mut call_func = |val: T| { + unsafe { + let state = &mut *self.state.get(); + + let func_ptr = match *state { + State::Waiting(p) => p, + _ => unreachable!(), + }; + + // Set state to Running while running the function to avoid reentrancy. + *state = State::Running; + + *state = match func(val) { + None => State::Waiting(func_ptr), + Some(res) => { + result.as_mut_ptr().write(res); + signal.signal(()); + State::None + } + }; + }; + }; + + let func_ptr: *mut dyn FnMut(T) = &mut call_func as _; + let func_ptr: *mut dyn FnMut(T) = unsafe { mem::transmute(func_ptr) }; + + unsafe { + let state = &mut *self.state.get(); + match *state { + State::None => {} + _ => depanic!("Multiple tasks waiting on same portal"), + } + *state = State::Waiting(func_ptr); + } + + signal.wait().await; + + bomb.defuse(); + + unsafe { result.assume_init() } + } + } +} diff --git a/embassy/src/util/signal.rs b/embassy/src/util/signal.rs new file mode 100644 index 000000000..32286a30e --- /dev/null +++ b/embassy/src/util/signal.rs @@ -0,0 +1,70 @@ +use core::cell::UnsafeCell; +use core::future::Future; +use core::mem; +use core::pin::Pin; +use core::task::{Context, Poll, Waker}; + +pub struct Signal { + state: UnsafeCell>, +} + +enum State { + None, + Waiting(Waker), + Signaled(T), +} + +unsafe impl Send for Signal {} +unsafe impl Sync for Signal {} + +impl Signal { + pub const fn new() -> Self { + Self { + state: UnsafeCell::new(State::None), + } + } + + pub fn signal(&self, val: T) { + unsafe { + cortex_m::interrupt::free(|_| { + let state = &mut *self.state.get(); + match mem::replace(state, State::Signaled(val)) { + State::Waiting(waker) => waker.wake(), + _ => {} + } + }) + } + } + + pub fn wait<'a>(&'a self) -> impl Future + 'a { + WaitFuture { signal: self } + } +} + +struct WaitFuture<'a, T> { + signal: &'a Signal, +} + +impl<'a, T: Send> Future for WaitFuture<'a, T> { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + unsafe { + cortex_m::interrupt::free(|_| { + let state = &mut *self.signal.state.get(); + match state { + State::None => { + *state = State::Waiting(cx.waker().clone()); + Poll::Pending + } + State::Waiting(w) if w.will_wake(cx.waker()) => Poll::Pending, + State::Waiting(_) => depanic!("waker overflow"), + State::Signaled(_) => match mem::replace(state, State::None) { + State::Signaled(res) => Poll::Ready(res), + _ => unreachable!(), + }, + } + }) + } + } +} diff --git a/embassy/src/util/waker_store.rs b/embassy/src/util/waker_store.rs new file mode 100644 index 000000000..0b2f09f4b --- /dev/null +++ b/embassy/src/util/waker_store.rs @@ -0,0 +1,23 @@ +use core::task::Waker; + +pub struct WakerStore { + waker: Option, +} + +impl WakerStore { + pub const fn new() -> Self { + Self { waker: None } + } + + pub fn store(&mut self, w: &Waker) { + match self.waker { + Some(ref w2) if (w2.will_wake(w)) => {} + Some(_) => panic!("Waker overflow"), + None => self.waker = Some(w.clone()), + } + } + + pub fn wake(&mut self) { + self.waker.take().map(|w| w.wake()); + } +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 000000000..c243691a7 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = ["Dario Nieuwenhuis "] +edition = "2018" +name = "embassy-examples" +version = "0.1.0" + +[features] +default = [ + "defmt-default", +] +defmt-default = [] +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + + +[dependencies] +cortex-m = { version = "0.6.3" } +cortex-m-rt = "0.6.12" +defmt = "0.1.0" +embedded-hal = { version = "0.2.4" } +defmt-rtt = "0.1.0" +panic-probe = "0.1.0" +nrf52840-hal = { version = "0.11.0" } +embassy = { version = "0.1.0", path = "../embassy" } +embassy-nrf = { version = "0.1.0", path = "../embassy-nrf", features = ["defmt-trace", "nrf52840"] } +static-executor = { version = "0.1.0", features=["defmt"]} +static-executor-cortex-m = { version = "0.1.0" } +futures = { version = "0.3.5", default-features = false } diff --git a/examples/build.rs b/examples/build.rs new file mode 100644 index 000000000..d534cc3df --- /dev/null +++ b/examples/build.rs @@ -0,0 +1,31 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/examples/memory.x b/examples/memory.x new file mode 100644 index 000000000..9b04edec0 --- /dev/null +++ b/examples/memory.x @@ -0,0 +1,7 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* These values correspond to the NRF52840 with Softdevices S140 7.0.1 */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/examples/src/bin/qspi.rs b/examples/src/bin/qspi.rs new file mode 100644 index 000000000..395422e7f --- /dev/null +++ b/examples/src/bin/qspi.rs @@ -0,0 +1,123 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; +use example_common::*; + +use cortex_m_rt::entry; +use embassy::flash::Flash; +use embassy_nrf::qspi; +use nrf52840_hal::gpio; + +const PAGE_SIZE: usize = 4096; + +// Workaround for alignment requirements. +// Nicer API will probably come in the future. +#[repr(C, align(4))] +struct AlignedBuf([u8; 4096]); + +#[static_executor::task] +async fn run() { + let p = embassy_nrf::pac::Peripherals::take().dewrap(); + + let port0 = gpio::p0::Parts::new(p.P0); + + let pins = qspi::Pins { + csn: port0 + .p0_17 + .into_push_pull_output(gpio::Level::High) + .degrade(), + sck: port0 + .p0_19 + .into_push_pull_output(gpio::Level::High) + .degrade(), + io0: port0 + .p0_20 + .into_push_pull_output(gpio::Level::High) + .degrade(), + io1: port0 + .p0_21 + .into_push_pull_output(gpio::Level::High) + .degrade(), + io2: Some( + port0 + .p0_22 + .into_push_pull_output(gpio::Level::High) + .degrade(), + ), + io3: Some( + port0 + .p0_23 + .into_push_pull_output(gpio::Level::High) + .degrade(), + ), + }; + + let config = qspi::Config { + pins, + read_opcode: qspi::ReadOpcode::READ4IO, + write_opcode: qspi::WriteOpcode::PP4IO, + xip_offset: 0, + write_page_size: qspi::WritePageSize::_256BYTES, + }; + + let mut q = qspi::Qspi::new(p.QSPI, config); + + let mut id = [1; 3]; + q.custom_instruction(0x9F, &[], &mut id).await.unwrap(); + info!("id: {:[u8]}", id); + + // Read status register + let mut status = [0; 1]; + q.custom_instruction(0x05, &[], &mut status).await.unwrap(); + + info!("status: {:?}", status[0]); + + if status[0] & 0x40 == 0 { + status[0] |= 0x40; + + q.custom_instruction(0x01, &status, &mut []).await.unwrap(); + + info!("enabled quad in status"); + } + + let mut buf = AlignedBuf([0u8; PAGE_SIZE]); + + let pattern = |a: u32| (a ^ (a >> 8) ^ (a >> 16) ^ (a >> 24)) as u8; + + for i in 0..8 { + info!("page {:?}: erasing... ", i); + q.erase(i * PAGE_SIZE).await.unwrap(); + + for j in 0..PAGE_SIZE { + buf.0[j] = pattern((j + i * PAGE_SIZE) as u32); + } + + info!("programming..."); + q.write(i * PAGE_SIZE, &buf.0).await.unwrap(); + } + + for i in 0..8 { + info!("page {:?}: reading... ", i); + q.read(i * PAGE_SIZE, &mut buf.0).await.unwrap(); + + info!("verifying..."); + for j in 0..PAGE_SIZE { + assert_eq!(buf.0[j], pattern((j + i * PAGE_SIZE) as u32)); + } + } + + info!("done!") +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + unsafe { + run.spawn().dewrap(); + static_executor::run(); + } +} diff --git a/examples/src/bin/uart.rs b/examples/src/bin/uart.rs new file mode 100644 index 000000000..21e26e3ad --- /dev/null +++ b/examples/src/bin/uart.rs @@ -0,0 +1,72 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; +use example_common::*; + +use cortex_m_rt::entry; +use embassy::io::{AsyncBufRead, AsyncBufReadExt, AsyncWrite, AsyncWriteExt}; +use embassy_nrf::uarte; +use futures::pin_mut; +use nrf52840_hal::gpio; + +#[static_executor::task] +async fn run() { + let p = embassy_nrf::pac::Peripherals::take().dewrap(); + + let port0 = gpio::p0::Parts::new(p.P0); + + let pins = uarte::Pins { + rxd: port0.p0_08.into_floating_input().degrade(), + txd: port0 + .p0_06 + .into_push_pull_output(gpio::Level::Low) + .degrade(), + cts: None, + rts: None, + }; + + let u = uarte::Uarte::new( + p.UARTE0, + pins, + uarte::Parity::EXCLUDED, + uarte::Baudrate::BAUD115200, + ); + pin_mut!(u); + + info!("uarte initialized!"); + + u.write_all(b"Hello!\r\n").await.dewrap(); + info!("wrote hello in uart!"); + + // Simple demo, reading 8-char chunks and echoing them back reversed. + loop { + info!("reading..."); + let mut buf = [0u8; 8]; + u.read_exact(&mut buf).await.dewrap(); + info!("read done, got {:[u8]}", buf); + + // Reverse buf + for i in 0..4 { + let tmp = buf[i]; + buf[i] = buf[7 - i]; + buf[7 - i] = tmp; + } + + info!("writing..."); + u.write_all(&buf).await.dewrap(); + info!("write done"); + } +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + unsafe { + run.spawn().dewrap(); + static_executor::run(); + } +} diff --git a/examples/src/example_common.rs b/examples/src/example_common.rs new file mode 100644 index 000000000..e9919153c --- /dev/null +++ b/examples/src/example_common.rs @@ -0,0 +1,68 @@ +#![macro_use] + +use defmt_rtt as _; // global logger +use nrf52840_hal as _; +use panic_probe as _; +use static_executor_cortex_m as _; + +pub use defmt::{info, intern}; + +use core::sync::atomic::{AtomicUsize, Ordering}; + +#[defmt::timestamp] +fn timestamp() -> u64 { + static COUNT: AtomicUsize = AtomicUsize::new(0); + // NOTE(no-CAS) `timestamps` runs with interrupts disabled + let n = COUNT.load(Ordering::Relaxed); + COUNT.store(n + 1, Ordering::Relaxed); + n as u64 +} + +macro_rules! depanic { + ($( $i:expr ),*) => { + { + defmt::error!($( $i ),*); + panic!(); + } + } +} + +pub trait Dewrap { + /// dewrap = defmt unwrap + fn dewrap(self) -> T; + + /// dexpect = defmt expect + fn dexpect(self, msg: M) -> T; +} + +impl Dewrap for Option { + fn dewrap(self) -> T { + match self { + Some(t) => t, + None => depanic!("Dewrap failed: enum is none"), + } + } + + fn dexpect(self, msg: M) -> T { + match self { + Some(t) => t, + None => depanic!("Unexpected None: {:?}", msg), + } + } +} + +impl Dewrap for Result { + fn dewrap(self) -> T { + match self { + Ok(t) => t, + Err(e) => depanic!("Dewrap failed: {:?}", e), + } + } + + fn dexpect(self, msg: M) -> T { + match self { + Ok(t) => t, + Err(e) => depanic!("Unexpected error: {:?}: {:?}", msg, e), + } + } +}