diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 767e4224..f1256320 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -12,11 +12,11 @@ env: jobs: all: runs-on: ubuntu-20.04 - needs: [build, test] + needs: [build-nightly, build-stable, test] steps: - name: Done run: exit 0 - build: + build-nightly: runs-on: ubuntu-latest permissions: id-token: write @@ -41,6 +41,28 @@ jobs: chmod +x /usr/local/bin/cargo-batch ./ci.sh rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* + build-stable: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: Cache multiple paths + uses: actions/cache@v2 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target_ci_stable + key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} + - name: build + run: | + curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.1.0/cargo-batch + chmod +x /usr/local/bin/cargo-batch + ./ci_stable.sh + rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* test: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 92f9a32b..144dd703 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target target_ci +target_ci_stable Cargo.lock third_party /Cargo.toml diff --git a/.vscode/settings.json b/.vscode/settings.json index 3defc907..79433a7c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,16 +4,19 @@ "rust-analyzer.assist.importGranularity": "module", "rust-analyzer.checkOnSave.allFeatures": false, "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.checkOnSave.noDefaultFeatures": true, + "rust-analyzer.cargo.allFeatures": false, "rust-analyzer.cargo.noDefaultFeatures": true, "rust-analyzer.experimental.procAttrMacros": false, - "rust-analyzer.checkOnSave.noDefaultFeatures": true, + "rust-analyzer.procMacro.enable": true, + "rust-analyzer.cargo.runBuildScripts": true, "rust-analyzer.cargo.target": "thumbv7em-none-eabi", "rust-analyzer.cargo.features": [ // These are needed to prevent embassy-net from failing to build //"embassy-net/medium-ethernet", //"embassy-net/tcp", //"embassy-net/pool-16", + "nightly", ], "rust-analyzer.linkedProjects": [ // Declare for the target you wish to develop @@ -35,12 +38,4 @@ // "examples/stm32wl55/Cargo.toml", // "examples/wasm/Cargo.toml", ], - "rust-analyzer.procMacro.enable": true, - "rust-analyzer.cargo.runBuildScripts": true, - "files.watcherExclude": { - "**/.git/objects/**": true, - "**/.git/subtree-cache/**": true, - "**/target/**": true - }, - "git.ignoreLimitWarning": true } \ No newline at end of file diff --git a/ci.sh b/ci.sh index 452130be..cd0db286 100755 --- a/ci.sh +++ b/ci.sh @@ -24,24 +24,24 @@ rm -rf stm32-metapac mv stm32-metapac-gen/out stm32-metapac cargo batch \ - --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi \ - --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi --features log,executor-agnostic \ - --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi --features defmt \ - --- build --release --manifest-path embassy/Cargo.toml --target thumbv6m-none-eabi --features defmt \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52820,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833,gpiote,time-driver-rtc1,unstable-traits \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-s,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-ns,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-net,gpiote,time-driver-rtc1,unstable-traits \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,log,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,defmt,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi --features nightly \ + --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi --features nightly,log,executor-agnostic \ + --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \ + --- build --release --manifest-path embassy/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52820,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-s,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-ns,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-net,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52840,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52840,log,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52840,defmt,gpiote,time-driver-rtc1,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,time-driver-any \ diff --git a/ci_stable.sh b/ci_stable.sh new file mode 100755 index 00000000..c4ec3082 --- /dev/null +++ b/ci_stable.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -euo pipefail + +export CARGO_TARGET_DIR=$PWD/target_ci_stable +export RUSTFLAGS=-Dwarnings +export DEFMT_LOG=trace + +sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml + +cargo batch \ + --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi \ + --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi --features log,executor-agnostic \ + --- build --release --manifest-path embassy/Cargo.toml --target thumbv7em-none-eabi --features defmt \ + --- build --release --manifest-path embassy/Cargo.toml --target thumbv6m-none-eabi --features defmt \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52820,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-s,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-ns,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-net,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,log,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,defmt,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf --bin raw_spawn \ diff --git a/docs/modules/ROOT/examples/basic/Cargo.toml b/docs/modules/ROOT/examples/basic/Cargo.toml index 0f1c30da..ed1c3cb1 100644 --- a/docs/modules/ROOT/examples/basic/Cargo.toml +++ b/docs/modules/ROOT/examples/basic/Cargo.toml @@ -5,8 +5,8 @@ name = "embassy-basic-example" version = "0.1.0" [dependencies] -embassy = { version = "0.1.0", path = "../../../../../embassy", features = ["defmt"] } -embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } +embassy = { version = "0.1.0", path = "../../../../../embassy", features = ["defmt", "nightly"] } +embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] } defmt = "0.3" defmt-rtt = "0.3" diff --git a/embassy-macros/src/chip/nrf.rs b/embassy-macros/src/chip/nrf.rs deleted file mode 100644 index 3ff6a74c..00000000 --- a/embassy-macros/src/chip/nrf.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::path::ModulePrefix; -use proc_macro2::TokenStream; -use quote::quote; - -pub fn generate(embassy_prefix: &ModulePrefix, config: syn::Expr) -> TokenStream { - let embassy_nrf_path = embassy_prefix.append("embassy_nrf").path(); - - quote!( - let p = #embassy_nrf_path::init(#config); - ) -} diff --git a/embassy-macros/src/chip/rp.rs b/embassy-macros/src/chip/rp.rs deleted file mode 100644 index ba0a97ad..00000000 --- a/embassy-macros/src/chip/rp.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::path::ModulePrefix; -use proc_macro2::TokenStream; -use quote::quote; - -pub fn generate(embassy_prefix: &ModulePrefix, config: syn::Expr) -> TokenStream { - let embassy_rp_path = embassy_prefix.append("embassy_rp").path(); - quote!( - let p = #embassy_rp_path::init(#config); - ) -} diff --git a/embassy-macros/src/chip/stm32.rs b/embassy-macros/src/chip/stm32.rs deleted file mode 100644 index c6938836..00000000 --- a/embassy-macros/src/chip/stm32.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::path::ModulePrefix; -use proc_macro2::TokenStream; -use quote::quote; - -pub fn generate(embassy_prefix: &ModulePrefix, config: syn::Expr) -> TokenStream { - let embassy_stm32_path = embassy_prefix.append("embassy_stm32").path(); - - quote!( - let p = #embassy_stm32_path::init(#config); - ) -} diff --git a/embassy-macros/src/lib.rs b/embassy-macros/src/lib.rs index 44a8d3b9..085f7889 100644 --- a/embassy-macros/src/lib.rs +++ b/embassy-macros/src/lib.rs @@ -1,228 +1,39 @@ -#![feature(proc_macro_diagnostic)] - extern crate proc_macro; -use darling::FromMeta; use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::{format_ident, quote}; -use std::iter; -use syn::spanned::Spanned; -use syn::{parse, Type, Visibility}; -use syn::{ItemFn, ReturnType}; -mod path; - -use path::ModulePrefix; - -#[derive(Debug, FromMeta)] -struct TaskArgs { - #[darling(default)] - pool_size: Option, - #[darling(default)] - send: bool, - #[darling(default)] - embassy_prefix: ModulePrefix, -} +mod macros; +mod util; +use macros::*; #[proc_macro_attribute] pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { - let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs); - let mut task_fn = syn::parse_macro_input!(item as syn::ItemFn); + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); - let macro_args = match TaskArgs::from_list(¯o_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; - - let embassy_prefix = macro_args.embassy_prefix.append("embassy"); - let embassy_path = embassy_prefix.path(); - - let pool_size: usize = macro_args.pool_size.unwrap_or(1); - - let mut fail = false; - if task_fn.sig.asyncness.is_none() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must be async") - .emit(); - fail = true; - } - if !task_fn.sig.generics.params.is_empty() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must not be generic") - .emit(); - fail = true; - } - if pool_size < 1 { - return parse::Error::new(Span::call_site(), "pool_size must be 1 or greater") - .to_compile_error() - .into(); - } - - let mut arg_names: syn::punctuated::Punctuated = - syn::punctuated::Punctuated::new(); - let mut args = task_fn.sig.inputs.clone(); - - for arg in args.iter_mut() { - match arg { - syn::FnArg::Receiver(_) => { - arg.span() - .unwrap() - .error("task functions must not have receiver arguments") - .emit(); - fail = true; - } - syn::FnArg::Typed(t) => match t.pat.as_mut() { - syn::Pat::Ident(i) => { - arg_names.push(i.ident.clone()); - i.mutability = None; - } - _ => { - arg.span() - .unwrap() - .error("pattern matching in task arguments is not yet supporteds") - .emit(); - fail = true; - } - }, - } - } - - if fail { - return TokenStream::new(); - } - - let name = task_fn.sig.ident.clone(); - - let visibility = &task_fn.vis; - task_fn.sig.ident = format_ident!("task"); - let impl_ty = if macro_args.send { - quote!(impl ::core::future::Future + Send + 'static) - } else { - quote!(impl ::core::future::Future + 'static) - }; - - let attrs = &task_fn.attrs; - - let result = quote! { - #(#attrs)* - #visibility fn #name(#args) -> #embassy_path::executor::SpawnToken<#impl_ty> { - use #embassy_path::executor::raw::TaskStorage; - #task_fn - type F = #impl_ty; - #[allow(clippy::declare_interior_mutable_const)] - const NEW_TASK: TaskStorage = TaskStorage::new(); - static POOL: [TaskStorage; #pool_size] = [NEW_TASK; #pool_size]; - unsafe { TaskStorage::spawn_pool(&POOL, move || task(#arg_names)) } - } - }; - result.into() + task::run(args, f).unwrap_or_else(|x| x).into() } #[proc_macro_attribute] -pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { - let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); +pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(args, f).unwrap_or_else(|x| x).into() +} - if !args.is_empty() { - return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") - .to_compile_error() - .into(); - } - - let fspan = f.span(); - let ident = f.sig.ident.clone(); - let ident_s = ident.to_string(); - - // XXX should we blacklist other attributes? - - let valid_signature = f.sig.constness.is_none() - && f.vis == Visibility::Inherited - && f.sig.abi.is_none() - && f.sig.inputs.is_empty() - && f.sig.generics.params.is_empty() - && f.sig.generics.where_clause.is_none() - && f.sig.variadic.is_none() - && match f.sig.output { - ReturnType::Default => true, - ReturnType::Type(_, ref ty) => match **ty { - Type::Tuple(ref tuple) => tuple.elems.is_empty(), - Type::Never(..) => true, - _ => false, - }, - }; - - if !valid_signature { - return parse::Error::new( - fspan, - "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`", - ) - .to_compile_error() - .into(); - } - - f.block.stmts = iter::once( - syn::parse2(quote! {{ - // Check that this interrupt actually exists - let __irq_exists_check: interrupt::#ident; - }}) - .unwrap(), - ) - .chain(f.block.stmts) - .collect(); - - quote!( - #[doc(hidden)] - #[export_name = #ident_s] - #[allow(non_snake_case)] - #f - ) - .into() +#[proc_macro_attribute] +pub fn interrupt(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); + interrupt::run(args, f).unwrap_or_else(|x| x).into() } #[proc_macro] pub fn interrupt_declare(item: TokenStream) -> TokenStream { let name = syn::parse_macro_input!(item as syn::Ident); - let name = format_ident!("{}", name); - let name_interrupt = format_ident!("{}", name); - let name_handler = format!("__EMBASSY_{}_HANDLER", name); - - let result = quote! { - #[allow(non_camel_case_types)] - pub struct #name_interrupt(()); - unsafe impl ::embassy::interrupt::Interrupt for #name_interrupt { - type Priority = crate::interrupt::Priority; - fn number(&self) -> u16 { - use cortex_m::interrupt::InterruptNumber; - let irq = InterruptEnum::#name; - irq.number() as u16 - } - unsafe fn steal() -> Self { - Self(()) - } - unsafe fn __handler(&self) -> &'static ::embassy::interrupt::Handler { - #[export_name = #name_handler] - static HANDLER: ::embassy::interrupt::Handler = ::embassy::interrupt::Handler::new(); - &HANDLER - } - } - - unsafe impl ::embassy::util::Unborrow for #name_interrupt { - type Target = #name_interrupt; - unsafe fn unborrow(self) -> #name_interrupt { - self - } - } - }; - result.into() + interrupt_declare::run(name).unwrap_or_else(|x| x).into() } + /// # interrupt_take procedural macro /// /// core::panic! is used as a default way to panic in this macro as there is no sensible way of enabling/disabling defmt for macro generation. @@ -231,312 +42,5 @@ pub fn interrupt_declare(item: TokenStream) -> TokenStream { #[proc_macro] pub fn interrupt_take(item: TokenStream) -> TokenStream { let name = syn::parse_macro_input!(item as syn::Ident); - let name = format!("{}", name); - let name_interrupt = format_ident!("{}", name); - let name_handler = format!("__EMBASSY_{}_HANDLER", name); - - let result = quote! { - { - #[allow(non_snake_case)] - #[export_name = #name] - pub unsafe extern "C" fn trampoline() { - extern "C" { - #[link_name = #name_handler] - static HANDLER: ::embassy::interrupt::Handler; - } - - let func = HANDLER.func.load(::embassy::export::atomic::Ordering::Relaxed); - let ctx = HANDLER.ctx.load(::embassy::export::atomic::Ordering::Relaxed); - let func: fn(*mut ()) = ::core::mem::transmute(func); - func(ctx) - } - - static TAKEN: ::embassy::export::atomic::AtomicBool = ::embassy::export::atomic::AtomicBool::new(false); - - if TAKEN.compare_exchange(false, true, ::embassy::export::atomic::Ordering::AcqRel, ::embassy::export::atomic::Ordering::Acquire).is_err() { - core::panic!("IRQ Already taken"); - } - - let irq: interrupt::#name_interrupt = unsafe { ::core::mem::transmute(()) }; - irq - } - }; - result.into() -} - -#[cfg(feature = "stm32")] -#[path = "chip/stm32.rs"] -mod chip; - -#[cfg(feature = "nrf")] -#[path = "chip/nrf.rs"] -mod chip; - -#[cfg(feature = "rp")] -#[path = "chip/rp.rs"] -mod chip; - -#[cfg(any( - feature = "nrf", - feature = "rp", - feature = "stm32", - feature = "wasm", - feature = "std" -))] -#[derive(Debug, FromMeta)] -struct MainArgs { - #[darling(default)] - embassy_prefix: ModulePrefix, - - #[allow(unused)] - #[darling(default)] - config: Option, -} - -#[cfg(any(feature = "nrf", feature = "rp", feature = "stm32"))] -#[proc_macro_attribute] -pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { - let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs); - let task_fn = syn::parse_macro_input!(item as syn::ItemFn); - - let macro_args = match MainArgs::from_list(¯o_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; - - let mut fail = false; - if task_fn.sig.asyncness.is_none() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must be async") - .emit(); - fail = true; - } - if !task_fn.sig.generics.params.is_empty() { - task_fn - .sig - .span() - .unwrap() - .error("main function must not be generic") - .emit(); - fail = true; - } - - let args = task_fn.sig.inputs.clone(); - - if args.len() != 2 { - task_fn - .sig - .span() - .unwrap() - .error("main function must have 2 arguments") - .emit(); - fail = true; - } - - if fail { - return TokenStream::new(); - } - - let embassy_prefix = macro_args.embassy_prefix; - let embassy_prefix_lit = embassy_prefix.literal(); - let embassy_path = embassy_prefix.append("embassy").path(); - let task_fn_body = task_fn.block; - - let config = macro_args - .config - .map(|s| s.parse::().unwrap()) - .unwrap_or_else(|| { - syn::Expr::Verbatim(quote! { - Default::default() - }) - }); - - let chip_setup = chip::generate(&embassy_prefix, config); - - let result = quote! { - #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)] - async fn __embassy_main(#args) { - #task_fn_body - } - - #[cortex_m_rt::entry] - fn main() -> ! { - unsafe fn make_static(t: &mut T) -> &'static mut T { - ::core::mem::transmute(t) - } - - #chip_setup - - let mut executor = #embassy_path::executor::Executor::new(); - let executor = unsafe { make_static(&mut executor) }; - - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner, p)); - }) - } - }; - result.into() -} - -#[cfg(feature = "std")] -#[proc_macro_attribute] -pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { - let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs); - let task_fn = syn::parse_macro_input!(item as syn::ItemFn); - - let macro_args = match MainArgs::from_list(¯o_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; - - let embassy_path = macro_args.embassy_prefix.append("embassy"); - - let mut fail = false; - if task_fn.sig.asyncness.is_none() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must be async") - .emit(); - fail = true; - } - if !task_fn.sig.generics.params.is_empty() { - task_fn - .sig - .span() - .unwrap() - .error("main function must not be generic") - .emit(); - fail = true; - } - - let args = task_fn.sig.inputs.clone(); - - if args.len() != 1 { - task_fn - .sig - .span() - .unwrap() - .error("main function must have one argument") - .emit(); - fail = true; - } - - if fail { - return TokenStream::new(); - } - - let task_fn_body = task_fn.block.clone(); - - let embassy_path = embassy_path.path(); - let embassy_prefix_lit = macro_args.embassy_prefix.literal(); - - let result = quote! { - #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)] - async fn __embassy_main(#args) { - #task_fn_body - } - - fn main() -> ! { - unsafe fn make_static(t: &mut T) -> &'static mut T { - ::core::mem::transmute(t) - } - - let mut executor = #embassy_path::executor::Executor::new(); - let executor = unsafe { make_static(&mut executor) }; - - executor.run(|spawner| { - spawner.spawn(__embassy_main(spawner)).unwrap(); - }) - - } - }; - result.into() -} - -#[cfg(feature = "wasm")] -#[proc_macro_attribute] -pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { - let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs); - let task_fn = syn::parse_macro_input!(item as syn::ItemFn); - - let macro_args = match MainArgs::from_list(¯o_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; - - let embassy_path = macro_args.embassy_prefix.append("embassy"); - - let mut fail = false; - if task_fn.sig.asyncness.is_none() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must be async") - .emit(); - fail = true; - } - if !task_fn.sig.generics.params.is_empty() { - task_fn - .sig - .span() - .unwrap() - .error("main function must not be generic") - .emit(); - fail = true; - } - - let args = task_fn.sig.inputs.clone(); - - if args.len() != 1 { - task_fn - .sig - .span() - .unwrap() - .error("main function must have one argument") - .emit(); - fail = true; - } - - if fail { - return TokenStream::new(); - } - - let task_fn_body = task_fn.block.clone(); - - let embassy_path = embassy_path.path(); - let embassy_prefix_lit = macro_args.embassy_prefix.literal(); - - let result = quote! { - #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)] - async fn __embassy_main(#args) { - #task_fn_body - } - - use wasm_bindgen::prelude::*; - - #[wasm_bindgen(start)] - pub fn main() -> Result<(), JsValue> { - static EXECUTOR: #embassy_path::util::Forever<#embassy_path::executor::Executor> = #embassy_path::util::Forever::new(); - let executor = EXECUTOR.put(#embassy_path::executor::Executor::new()); - - executor.start(|spawner| { - spawner.spawn(__embassy_main(spawner)).unwrap(); - }); - - Ok(()) - } - }; - result.into() + interrupt_take::run(name).unwrap_or_else(|x| x).into() } diff --git a/embassy-macros/src/macros/interrupt.rs b/embassy-macros/src/macros/interrupt.rs new file mode 100644 index 00000000..32cc0e01 --- /dev/null +++ b/embassy-macros/src/macros/interrupt.rs @@ -0,0 +1,66 @@ +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::quote; +use std::iter; +use syn::ReturnType; +use syn::{Type, Visibility}; + +use crate::util::ctxt::Ctxt; + +#[derive(Debug, FromMeta)] +struct Args {} + +pub fn run(args: syn::AttributeArgs, mut f: syn::ItemFn) -> Result { + let _args = Args::from_list(&args).map_err(|e| e.write_errors())?; + + let ident = f.sig.ident.clone(); + let ident_s = ident.to_string(); + + // XXX should we blacklist other attributes? + + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }; + + let ctxt = Ctxt::new(); + + if !valid_signature { + ctxt.error_spanned_by( + &f.sig, + "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`", + ); + } + + ctxt.check()?; + + f.block.stmts = iter::once( + syn::parse2(quote! {{ + // Check that this interrupt actually exists + let __irq_exists_check: interrupt::#ident; + }}) + .unwrap(), + ) + .chain(f.block.stmts) + .collect(); + + let result = quote!( + #[doc(hidden)] + #[export_name = #ident_s] + #[allow(non_snake_case)] + #f + ); + + Ok(result) +} diff --git a/embassy-macros/src/macros/interrupt_declare.rs b/embassy-macros/src/macros/interrupt_declare.rs new file mode 100644 index 00000000..0059936d --- /dev/null +++ b/embassy-macros/src/macros/interrupt_declare.rs @@ -0,0 +1,37 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +pub fn run(name: syn::Ident) -> Result { + let name = format_ident!("{}", name); + let name_interrupt = format_ident!("{}", name); + let name_handler = format!("__EMBASSY_{}_HANDLER", name); + + let result = quote! { + #[allow(non_camel_case_types)] + pub struct #name_interrupt(()); + unsafe impl ::embassy::interrupt::Interrupt for #name_interrupt { + type Priority = crate::interrupt::Priority; + fn number(&self) -> u16 { + use cortex_m::interrupt::InterruptNumber; + let irq = InterruptEnum::#name; + irq.number() as u16 + } + unsafe fn steal() -> Self { + Self(()) + } + unsafe fn __handler(&self) -> &'static ::embassy::interrupt::Handler { + #[export_name = #name_handler] + static HANDLER: ::embassy::interrupt::Handler = ::embassy::interrupt::Handler::new(); + &HANDLER + } + } + + unsafe impl ::embassy::util::Unborrow for #name_interrupt { + type Target = #name_interrupt; + unsafe fn unborrow(self) -> #name_interrupt { + self + } + } + }; + Ok(result) +} diff --git a/embassy-macros/src/macros/interrupt_take.rs b/embassy-macros/src/macros/interrupt_take.rs new file mode 100644 index 00000000..230b9c74 --- /dev/null +++ b/embassy-macros/src/macros/interrupt_take.rs @@ -0,0 +1,36 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +pub fn run(name: syn::Ident) -> Result { + let name = format!("{}", name); + let name_interrupt = format_ident!("{}", name); + let name_handler = format!("__EMBASSY_{}_HANDLER", name); + + let result = quote! { + { + #[allow(non_snake_case)] + #[export_name = #name] + pub unsafe extern "C" fn trampoline() { + extern "C" { + #[link_name = #name_handler] + static HANDLER: ::embassy::interrupt::Handler; + } + + let func = HANDLER.func.load(::embassy::export::atomic::Ordering::Relaxed); + let ctx = HANDLER.ctx.load(::embassy::export::atomic::Ordering::Relaxed); + let func: fn(*mut ()) = ::core::mem::transmute(func); + func(ctx) + } + + static TAKEN: ::embassy::export::atomic::AtomicBool = ::embassy::export::atomic::AtomicBool::new(false); + + if TAKEN.compare_exchange(false, true, ::embassy::export::atomic::Ordering::AcqRel, ::embassy::export::atomic::Ordering::Acquire).is_err() { + core::panic!("IRQ Already taken"); + } + + let irq: interrupt::#name_interrupt = unsafe { ::core::mem::transmute(()) }; + irq + } + }; + Ok(result) +} diff --git a/embassy-macros/src/macros/main.rs b/embassy-macros/src/macros/main.rs new file mode 100644 index 00000000..01e30292 --- /dev/null +++ b/embassy-macros/src/macros/main.rs @@ -0,0 +1,135 @@ +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::quote; + +use crate::util::ctxt::Ctxt; +use crate::util::path::ModulePrefix; + +#[cfg(feature = "stm32")] +const HAL: Option<&str> = Some("embassy_stm32"); +#[cfg(feature = "nrf")] +const HAL: Option<&str> = Some("embassy_nrf"); +#[cfg(feature = "rp")] +const HAL: Option<&str> = Some("embassy_rp"); +#[cfg(not(any(feature = "stm32", feature = "nrf", feature = "rp")))] +const HAL: Option<&str> = None; + +#[derive(Debug, FromMeta)] +struct Args { + #[darling(default)] + embassy_prefix: ModulePrefix, + + #[allow(unused)] + #[darling(default)] + config: Option, +} + +pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result { + let args = Args::from_list(&args).map_err(|e| e.write_errors())?; + + let fargs = f.sig.inputs.clone(); + + let ctxt = Ctxt::new(); + + if f.sig.asyncness.is_none() { + ctxt.error_spanned_by(&f.sig, "task functions must be async"); + } + if !f.sig.generics.params.is_empty() { + ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); + } + + if HAL.is_some() && fargs.len() != 2 { + ctxt.error_spanned_by(&f.sig, "main function must have 2 arguments"); + } + if HAL.is_none() && fargs.len() != 1 { + ctxt.error_spanned_by(&f.sig, "main function must have 2 arguments"); + } + + ctxt.check()?; + + let embassy_prefix = args.embassy_prefix; + let embassy_prefix_lit = embassy_prefix.literal(); + let embassy_path = embassy_prefix.append("embassy").path(); + let f_body = f.block; + + #[cfg(feature = "wasm")] + let main = quote! { + #[wasm_bindgen::prelude::wasm_bindgen(start)] + pub fn main() -> Result<(), wasm_bindgen::JsValue> { + static EXECUTOR: #embassy_path::util::Forever<#embassy_path::executor::Executor> = #embassy_path::util::Forever::new(); + let executor = EXECUTOR.put(#embassy_path::executor::Executor::new()); + + executor.start(|spawner| { + spawner.spawn(__embassy_main(spawner)).unwrap(); + }); + + Ok(()) + } + }; + + #[cfg(all(feature = "std", not(feature = "wasm")))] + let main = quote! { + fn main() -> ! { + let mut executor = #embassy_path::executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } + }; + + #[cfg(all(not(feature = "std"), not(feature = "wasm")))] + let main = { + let config = args + .config + .map(|s| s.parse::().unwrap()) + .unwrap_or_else(|| { + syn::Expr::Verbatim(quote! { + Default::default() + }) + }); + + let (hal_setup, peris_arg) = match HAL { + Some(hal) => { + let embassy_hal_path = embassy_prefix.append(hal).path(); + ( + quote!( + let p = #embassy_hal_path::init(#config); + ), + quote!(p), + ) + } + None => (quote!(), quote!()), + }; + + quote! { + #[cortex_m_rt::entry] + fn main() -> ! { + #hal_setup + + let mut executor = #embassy_path::executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner, #peris_arg)); + }) + } + } + }; + + let result = quote! { + #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)] + async fn __embassy_main(#fargs) { + #f_body + } + + unsafe fn __make_static(t: &mut T) -> &'static mut T { + ::core::mem::transmute(t) + } + + #main + }; + + Ok(result) +} diff --git a/embassy-macros/src/macros/mod.rs b/embassy-macros/src/macros/mod.rs new file mode 100644 index 00000000..4350f229 --- /dev/null +++ b/embassy-macros/src/macros/mod.rs @@ -0,0 +1,5 @@ +pub mod interrupt; +pub mod interrupt_declare; +pub mod interrupt_take; +pub mod main; +pub mod task; diff --git a/embassy-macros/src/macros/task.rs b/embassy-macros/src/macros/task.rs new file mode 100644 index 00000000..f0c78c59 --- /dev/null +++ b/embassy-macros/src/macros/task.rs @@ -0,0 +1,90 @@ +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::util::ctxt::Ctxt; +use crate::util::path::ModulePrefix; + +#[derive(Debug, FromMeta)] +struct Args { + #[darling(default)] + pool_size: Option, + #[darling(default)] + send: bool, + #[darling(default)] + embassy_prefix: ModulePrefix, +} + +pub fn run(args: syn::AttributeArgs, mut f: syn::ItemFn) -> Result { + let args = Args::from_list(&args).map_err(|e| e.write_errors())?; + + let embassy_prefix = args.embassy_prefix.append("embassy"); + let embassy_path = embassy_prefix.path(); + + let pool_size: usize = args.pool_size.unwrap_or(1); + + let ctxt = Ctxt::new(); + + if f.sig.asyncness.is_none() { + ctxt.error_spanned_by(&f.sig, "task functions must be async"); + } + if !f.sig.generics.params.is_empty() { + ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); + } + if pool_size < 1 { + ctxt.error_spanned_by(&f.sig, "pool_size must be 1 or greater"); + } + + let mut arg_names: syn::punctuated::Punctuated = + syn::punctuated::Punctuated::new(); + let mut fargs = f.sig.inputs.clone(); + + for arg in fargs.iter_mut() { + match arg { + syn::FnArg::Receiver(_) => { + ctxt.error_spanned_by(arg, "task functions must not have receiver arguments"); + } + syn::FnArg::Typed(t) => match t.pat.as_mut() { + syn::Pat::Ident(i) => { + arg_names.push(i.ident.clone()); + i.mutability = None; + } + _ => { + ctxt.error_spanned_by( + arg, + "pattern matching in task arguments is not yet supporteds", + ); + } + }, + } + } + + ctxt.check()?; + + let name = f.sig.ident.clone(); + + let visibility = &f.vis; + f.sig.ident = format_ident!("task"); + let impl_ty = if args.send { + quote!(impl ::core::future::Future + Send + 'static) + } else { + quote!(impl ::core::future::Future + 'static) + }; + + let attrs = &f.attrs; + + let result = quote! { + #(#attrs)* + #visibility fn #name(#fargs) -> #embassy_path::executor::SpawnToken<#impl_ty> { + use #embassy_path::executor::raw::TaskStorage; + #f + type F = #impl_ty; + #[allow(clippy::declare_interior_mutable_const)] + const NEW_TASK: TaskStorage = TaskStorage::new(); + static POOL: [TaskStorage; #pool_size] = [NEW_TASK; #pool_size]; + unsafe { TaskStorage::spawn_pool(&POOL, move || task(#arg_names)) } + } + }; + + Ok(result) +} diff --git a/embassy-macros/src/util/ctxt.rs b/embassy-macros/src/util/ctxt.rs new file mode 100644 index 00000000..d668ae78 --- /dev/null +++ b/embassy-macros/src/util/ctxt.rs @@ -0,0 +1,72 @@ +// nifty utility borrowed from serde :) +// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use std::cell::RefCell; +use std::fmt::Display; +use std::thread; +use syn; + +/// A type to collect errors together and format them. +/// +/// Dropping this object will cause a panic. It must be consumed using `check`. +/// +/// References can be shared since this type uses run-time exclusive mut checking. +#[derive(Default)] +pub struct Ctxt { + // The contents will be set to `None` during checking. This is so that checking can be + // enforced. + errors: RefCell>>, +} + +impl Ctxt { + /// Create a new context object. + /// + /// This object contains no errors, but will still trigger a panic if it is not `check`ed. + pub fn new() -> Self { + Ctxt { + errors: RefCell::new(Some(Vec::new())), + } + } + + /// Add an error to the context object with a tokenenizable object. + /// + /// The object is used for spanning in error messages. + pub fn error_spanned_by(&self, obj: A, msg: T) { + self.errors + .borrow_mut() + .as_mut() + .unwrap() + // Curb monomorphization from generating too many identical methods. + .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); + } + + /// Add one of Syn's parse errors. + #[allow(unused)] + pub fn syn_error(&self, err: syn::Error) { + self.errors.borrow_mut().as_mut().unwrap().push(err); + } + + /// Consume this object, producing a formatted error string if there are errors. + pub fn check(self) -> Result<(), TokenStream> { + let errors = self.errors.borrow_mut().take().unwrap(); + match errors.len() { + 0 => Ok(()), + _ => Err(to_compile_errors(errors)), + } + } +} + +fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream { + let compile_errors = errors.iter().map(syn::Error::to_compile_error); + quote!(#(#compile_errors)*) +} + +impl Drop for Ctxt { + fn drop(&mut self) { + if !thread::panicking() && self.errors.borrow().is_some() { + panic!("forgot to check for errors"); + } + } +} diff --git a/embassy-macros/src/util/mod.rs b/embassy-macros/src/util/mod.rs new file mode 100644 index 00000000..c2f2dfd6 --- /dev/null +++ b/embassy-macros/src/util/mod.rs @@ -0,0 +1,2 @@ +pub mod ctxt; +pub mod path; diff --git a/embassy-macros/src/path.rs b/embassy-macros/src/util/path.rs similarity index 100% rename from embassy-macros/src/path.rs rename to embassy-macros/src/util/path.rs diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 4ed922ba..5ced9b1b 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -6,14 +6,18 @@ edition = "2018" [features] +# Enable nightly-only features +nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async"] + # Reexport the PAC for the currently enabled chip at `embassy_nrf::pac`. # This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version. # If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. # There are no plans to make this stable. unstable-pac = [] -# Implement embedded-hal 1.0 alpha and embedded-hal-async traits. -unstable-traits = ["embedded-hal-1", "embedded-hal-async"] +# Implement embedded-hal 1.0 alpha traits. +# Implement embedded-hal-async traits if `nightly` is set as well. +unstable-traits = ["embedded-hal-1"] nrf52805 = ["nrf52805-pac", "_ppi"] nrf52810 = ["nrf52810-pac", "_ppi"] diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index a4c24058..b856c2df 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -463,7 +463,6 @@ mod eh02 { #[cfg(feature = "unstable-traits")] mod eh1 { use super::*; - use futures::FutureExt; impl<'d, C: Channel, T: GpioPin> embedded_hal_1::digital::ErrorType for InputChannel<'d, C, T> { type Error = Infallible; @@ -480,6 +479,12 @@ mod eh1 { self.pin.is_low() } } +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eh1a { + use super::*; + use futures::FutureExt; impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Input<'d, T> { type WaitForHighFuture<'a> diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 8e05d9b6..b448f6ab 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -1,6 +1,8 @@ #![no_std] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] +#![cfg_attr( + feature = "nightly", + feature(generic_associated_types, type_alias_impl_trait) +)] #[cfg(not(any( feature = "nrf51", diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs index 976a546c..a45b9390 100644 --- a/embassy-nrf/src/spim.rs +++ b/embassy-nrf/src/spim.rs @@ -374,7 +374,6 @@ mod eh02 { #[cfg(feature = "unstable-traits")] mod eh1 { use super::*; - use core::future::Future; impl embedded_hal_1::spi::Error for Error { fn kind(&self) -> embedded_hal_1::spi::ErrorKind { @@ -437,7 +436,7 @@ mod eh1 { fn transaction<'a>( &mut self, - operations: &mut [embedded_hal_async::spi::Operation<'a, u8>], + operations: &mut [embedded_hal_1::spi::blocking::Operation<'a, u8>], ) -> Result<(), Self::Error> { use embedded_hal_1::spi::blocking::Operation; for o in operations { @@ -451,6 +450,12 @@ mod eh1 { Ok(()) } } +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eh1a { + use super::*; + use core::future::Future; impl<'d, T: Instance> embedded_hal_async::spi::Read for Spim<'d, T> { type ReadFuture<'a> diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index 19356c2d..4240b9ac 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -2,6 +2,7 @@ use core::cell::Cell; use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; use core::{mem, ptr}; use critical_section::CriticalSection; +use embassy::blocking_mutex::raw::CriticalSectionRawMutex; use embassy::blocking_mutex::CriticalSectionMutex as Mutex; use embassy::interrupt::{Interrupt, InterruptExt}; use embassy::time::driver::{AlarmHandle, Driver}; @@ -94,7 +95,7 @@ const ALARM_STATE_NEW: AlarmState = AlarmState::new(); embassy::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { period: AtomicU32::new(0), alarm_count: AtomicU8::new(0), - alarms: Mutex::new([ALARM_STATE_NEW; ALARM_COUNT]), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]), }); impl RtcDriver { diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index 4cd47c89..ed2844f7 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -679,7 +679,7 @@ mod eh1 { fn transaction<'a>( &mut self, _address: u8, - _operations: &mut [embedded_hal_async::i2c::Operation<'a>], + _operations: &mut [embedded_hal_1::i2c::blocking::Operation<'a>], ) -> Result<(), Self::Error> { todo!(); } @@ -690,58 +690,59 @@ mod eh1 { _operations: O, ) -> Result<(), Self::Error> where - O: IntoIterator>, + O: IntoIterator>, { todo!(); } } +} - impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { - type ReadFuture<'a> - where - Self: 'a, - = impl Future> + 'a; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { + type ReadFuture<'a> + where + Self: 'a, + = impl Future> + 'a; - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(address, buffer) - } + fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { + self.read(address, buffer) + } - type WriteFuture<'a> - where - Self: 'a, - = impl Future> + 'a; + type WriteFuture<'a> + where + Self: 'a, + = impl Future> + 'a; - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(address, bytes) - } + fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { + self.write(address, bytes) + } - type WriteReadFuture<'a> - where - Self: 'a, - = impl Future> + 'a; + type WriteReadFuture<'a> + where + Self: 'a, + = impl Future> + 'a; - fn write_read<'a>( - &'a mut self, - address: u8, - wr_buffer: &'a [u8], - rd_buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - self.write_read(address, wr_buffer, rd_buffer) - } + fn write_read<'a>( + &'a mut self, + address: u8, + wr_buffer: &'a [u8], + rd_buffer: &'a mut [u8], + ) -> Self::WriteReadFuture<'a> { + self.write_read(address, wr_buffer, rd_buffer) + } - type TransactionFuture<'a> - where - Self: 'a, - = impl Future> + 'a; + type TransactionFuture<'a> + where + Self: 'a, + = impl Future> + 'a; - fn transaction<'a>( - &'a mut self, - address: u8, - operations: &mut [embedded_hal_async::i2c::Operation<'a>], - ) -> Self::TransactionFuture<'a> { - let _ = address; - let _ = operations; - async move { todo!() } - } + fn transaction<'a>( + &'a mut self, + address: u8, + operations: &mut [embedded_hal_async::i2c::Operation<'a>], + ) -> Self::TransactionFuture<'a> { + let _ = address; + let _ = operations; + async move { todo!() } } } diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index a1c47cff..119a1798 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -844,7 +844,6 @@ mod eh02 { #[cfg(feature = "unstable-traits")] mod eh1 { use super::*; - use core::future::Future; impl embedded_hal_1::serial::Error for Error { fn kind(&self) -> embedded_hal_1::serial::ErrorKind { @@ -872,6 +871,36 @@ mod eh1 { } } + impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UarteTx<'d, T> { + type Error = Error; + } + + impl<'d, T: Instance> embedded_hal_1::serial::blocking::Write for UarteTx<'d, T> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + } + + impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UarteRx<'d, T> { + type Error = Error; + } + + impl<'d, U: Instance, T: TimerInstance> embedded_hal_1::serial::ErrorType + for UarteWithIdle<'d, U, T> + { + type Error = Error; + } +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eh1a { + use super::*; + use core::future::Future; + impl<'d, T: Instance> embedded_hal_async::serial::Read for Uarte<'d, T> { type ReadFuture<'a> where @@ -903,22 +932,6 @@ mod eh1 { } } - // ===================== - - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UarteTx<'d, T> { - type Error = Error; - } - - impl<'d, T: Instance> embedded_hal_1::serial::blocking::Write for UarteTx<'d, T> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - } - impl<'d, T: Instance> embedded_hal_async::serial::Write for UarteTx<'d, T> { type WriteFuture<'a> where @@ -939,12 +952,6 @@ mod eh1 { } } - // ===================== - - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UarteRx<'d, T> { - type Error = Error; - } - impl<'d, T: Instance> embedded_hal_async::serial::Read for UarteRx<'d, T> { type ReadFuture<'a> where @@ -956,14 +963,6 @@ mod eh1 { } } - // ===================== - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_1::serial::ErrorType - for UarteWithIdle<'d, U, T> - { - type Error = Error; - } - impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Read for UarteWithIdle<'d, U, T> { diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index be41f95e..cb654402 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" unstable-pac = [] [dependencies] -embassy = { version = "0.1.0", path = "../embassy", features = [ "time-tick-1mhz" ] } +embassy = { version = "0.1.0", path = "../embassy", features = [ "time-tick-1mhz", "nightly"] } embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["rp"]} atomic-polyfill = "0.1.5" diff --git a/embassy-rp/src/timer.rs b/embassy-rp/src/timer.rs index b3c047ca..f449df00 100644 --- a/embassy-rp/src/timer.rs +++ b/embassy-rp/src/timer.rs @@ -1,7 +1,8 @@ use atomic_polyfill::{AtomicU8, Ordering}; use core::cell::Cell; use critical_section::CriticalSection; -use embassy::blocking_mutex::CriticalSectionMutex as Mutex; +use embassy::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy::blocking_mutex::Mutex; use embassy::interrupt::{Interrupt, InterruptExt}; use embassy::time::driver::{AlarmHandle, Driver}; @@ -20,12 +21,12 @@ const DUMMY_ALARM: AlarmState = AlarmState { }; struct TimerDriver { - alarms: Mutex<[AlarmState; ALARM_COUNT]>, + alarms: Mutex, next_alarm: AtomicU8, } embassy::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{ - alarms: Mutex::new([DUMMY_ALARM; ALARM_COUNT]), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [DUMMY_ALARM; ALARM_COUNT]), next_alarm: AtomicU8::new(0), }); diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 386722d4..55a646d4 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" resolver = "2" [dependencies] -embassy = { version = "0.1.0", path = "../embassy" } +embassy = { version = "0.1.0", path = "../embassy", features = ["nightly"]} embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["stm32"] } embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } embassy-net = { version = "0.1.0", path = "../embassy-net", default-features = false, optional = true } diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index d1596c5f..7efe0d3a 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -3,13 +3,15 @@ use core::cell::Cell; use core::convert::TryInto; use core::sync::atomic::{compiler_fence, Ordering}; use core::{mem, ptr}; +use embassy::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy::blocking_mutex::Mutex; use embassy::interrupt::InterruptExt; use embassy::time::driver::{AlarmHandle, Driver}; use embassy::time::TICKS_PER_SECOND; use stm32_metapac::timer::regs; use crate::interrupt; -use crate::interrupt::{CriticalSection, Interrupt, Mutex}; +use crate::interrupt::{CriticalSection, Interrupt}; use crate::pac::timer::{vals, TimGp16}; use crate::peripherals; use crate::rcc::sealed::RccPeripheral; @@ -95,7 +97,7 @@ struct RtcDriver { period: AtomicU32, alarm_count: AtomicU8, /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. - alarms: Mutex<[AlarmState; ALARM_COUNT]>, + alarms: Mutex, } const ALARM_STATE_NEW: AlarmState = AlarmState::new(); @@ -103,7 +105,7 @@ const ALARM_STATE_NEW: AlarmState = AlarmState::new(); embassy::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { period: AtomicU32::new(0), alarm_count: AtomicU8::new(0), - alarms: Mutex::new([ALARM_STATE_NEW; ALARM_COUNT]), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]), }); impl RtcDriver { diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml index ccb5574d..d10a8874 100644 --- a/embassy/Cargo.toml +++ b/embassy/Cargo.toml @@ -10,8 +10,12 @@ default = [] std = ["futures/std", "time", "time-tick-1mhz", "embassy-macros/std"] wasm = ["wasm-bindgen", "js-sys", "embassy-macros/wasm", "wasm-timer", "time", "time-tick-1mhz"] +# Enable nightly-only features +nightly = ["embedded-hal-async"] + # Implement embedded-hal 1.0 alpha and embedded-hal-async traits. -unstable-traits = ["embedded-hal-1", "embedded-hal-async"] +# Implement embedded-hal-async traits if `nightly` is set as well. +unstable-traits = ["embedded-hal-1"] # Enable `embassy::time` module. # NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. diff --git a/embassy/src/blocking_mutex/kind.rs b/embassy/src/blocking_mutex/kind.rs deleted file mode 100644 index a4a45605..00000000 --- a/embassy/src/blocking_mutex/kind.rs +++ /dev/null @@ -1,20 +0,0 @@ -pub trait MutexKind { - type Mutex: super::Mutex; -} - -pub enum CriticalSection {} -impl MutexKind for CriticalSection { - type Mutex = super::CriticalSectionMutex; -} - -#[cfg(any(cortex_m, feature = "std"))] -pub enum ThreadMode {} -#[cfg(any(cortex_m, feature = "std"))] -impl MutexKind for ThreadMode { - type Mutex = super::ThreadModeMutex; -} - -pub enum Noop {} -impl MutexKind for Noop { - type Mutex = super::NoopMutex; -} diff --git a/embassy/src/blocking_mutex/mod.rs b/embassy/src/blocking_mutex/mod.rs index 94953139..859eca07 100644 --- a/embassy/src/blocking_mutex/mod.rs +++ b/embassy/src/blocking_mutex/mod.rs @@ -1,67 +1,107 @@ //! Blocking mutex (not async) -pub mod kind; +pub mod raw; +use self::raw::RawMutex; use core::cell::UnsafeCell; -use critical_section::CriticalSection; /// Any object implementing this trait guarantees exclusive access to the data contained /// within the mutex for the duration of the lock. /// Adapted from . -pub trait Mutex { - /// Data protected by the mutex. - type Data; - - fn new(data: Self::Data) -> Self; - - /// Creates a critical section and grants temporary access to the protected data. - fn lock(&self, f: impl FnOnce(&Self::Data) -> R) -> R; +pub struct Mutex { + // NOTE: `raw` must be FIRST, so when using ThreadModeMutex the "can't drop in non-thread-mode" gets + // to run BEFORE dropping `data`. + raw: R, + data: UnsafeCell, } -/// A "mutex" based on critical sections -/// -/// # Safety -/// -/// **This Mutex is only safe on single-core systems.** -/// -/// On multi-core systems, a `CriticalSection` **is not sufficient** to ensure exclusive access. -pub struct CriticalSectionMutex { - inner: UnsafeCell, -} +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} -// NOTE: A `CriticalSectionMutex` can be used as a channel so the protected data must be `Send` -// to prevent sending non-Sendable stuff (e.g. access tokens) across different -// execution contexts (e.g. interrupts) -unsafe impl Sync for CriticalSectionMutex where T: Send {} - -impl CriticalSectionMutex { - /// Creates a new mutex - pub const fn new(value: T) -> Self { - CriticalSectionMutex { - inner: UnsafeCell::new(value), +impl Mutex { + /// Creates a new mutex in an unlocked state ready for use. + #[cfg(feature = "nightly")] + #[inline] + pub const fn new(val: T) -> Mutex { + Mutex { + raw: R::INIT, + data: UnsafeCell::new(val), } } + + /// Creates a new mutex in an unlocked state ready for use. + #[cfg(not(feature = "nightly"))] + #[inline] + pub fn new(val: T) -> Mutex { + Mutex { + raw: R::INIT, + data: UnsafeCell::new(val), + } + } + + /// Creates a critical section and grants temporary access to the protected data. + pub fn lock(&self, f: impl FnOnce(&T) -> U) -> U { + self.raw.lock(|| { + let ptr = self.data.get() as *const T; + let inner = unsafe { &*ptr }; + f(inner) + }) + } } -impl CriticalSectionMutex { +impl Mutex { + /// Creates a new mutex based on a pre-existing raw mutex. + /// + /// This allows creating a mutex in a constant context on stable Rust. + #[inline] + pub const fn const_new(raw_mutex: R, val: T) -> Mutex { + Mutex { + raw: raw_mutex, + data: UnsafeCell::new(val), + } + } + + /// Consumes this mutex, returning the underlying data. + #[inline] + pub fn into_inner(self) -> T { + self.data.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the `Mutex` mutably, no actual locking needs to + /// take place---the mutable borrow statically guarantees no locks exist. + #[inline] + pub fn get_mut(&mut self) -> &mut T { + unsafe { &mut *self.data.get() } + } +} + +pub type CriticalSectionMutex = Mutex; +pub type NoopMutex = Mutex; + +impl Mutex { /// Borrows the data for the duration of the critical section - pub fn borrow<'cs>(&'cs self, _cs: CriticalSection<'cs>) -> &'cs T { - unsafe { &*self.inner.get() } + pub fn borrow<'cs>(&'cs self, _cs: critical_section::CriticalSection<'cs>) -> &'cs T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } } } -impl Mutex for CriticalSectionMutex { - type Data = T; - - fn new(data: T) -> Self { - Self::new(data) - } - - fn lock(&self, f: impl FnOnce(&Self::Data) -> R) -> R { - critical_section::with(|cs| f(self.borrow(cs))) +impl Mutex { + /// Borrows the data + pub fn borrow(&self) -> &T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } } } +// ThreadModeMutex does NOT use the generic mutex from above because it's special: +// it's Send+Sync even if T: !Send. There's no way to do that without specialization (I think?). +// +// There's still a ThreadModeRawMutex for use with the generic Mutex (handy with Channel, for example), +// but that will require T: Send even though it shouldn't be needed. + #[cfg(any(cortex_m, feature = "std"))] pub use thread_mode_mutex::*; #[cfg(any(cortex_m, feature = "std"))] @@ -75,15 +115,15 @@ mod thread_mode_mutex { /// **This Mutex is only safe on single-core systems.** /// /// On multi-core systems, a `ThreadModeMutex` **is not sufficient** to ensure exclusive access. - pub struct ThreadModeMutex { + pub struct ThreadModeMutex { inner: UnsafeCell, } // NOTE: ThreadModeMutex only allows borrowing from one execution context ever: thread mode. // Therefore it cannot be used to send non-sendable stuff between execution contexts, so it can // be Send+Sync even if T is not Send (unlike CriticalSectionMutex) - unsafe impl Sync for ThreadModeMutex {} - unsafe impl Send for ThreadModeMutex {} + unsafe impl Sync for ThreadModeMutex {} + unsafe impl Send for ThreadModeMutex {} impl ThreadModeMutex { /// Creates a new mutex @@ -92,79 +132,35 @@ mod thread_mode_mutex { inner: UnsafeCell::new(value), } } + } + + impl ThreadModeMutex { + pub fn lock(&self, f: impl FnOnce(&T) -> R) -> R { + f(self.borrow()) + } /// Borrows the data pub fn borrow(&self) -> &T { assert!( - in_thread_mode(), + raw::in_thread_mode(), "ThreadModeMutex can only be borrowed from thread mode." ); unsafe { &*self.inner.get() } } } - impl Mutex for ThreadModeMutex { - type Data = T; - - fn new(data: T) -> Self { - Self::new(data) - } - - fn lock(&self, f: impl FnOnce(&Self::Data) -> R) -> R { - f(self.borrow()) - } - } - - impl Drop for ThreadModeMutex { + impl Drop for ThreadModeMutex { fn drop(&mut self) { // Only allow dropping from thread mode. Dropping calls drop on the inner `T`, so // `drop` needs the same guarantees as `lock`. `ThreadModeMutex` is Send even if // T isn't, so without this check a user could create a ThreadModeMutex in thread mode, // send it to interrupt context and drop it there, which would "send" a T even if T is not Send. assert!( - in_thread_mode(), + raw::in_thread_mode(), "ThreadModeMutex can only be dropped from thread mode." ); // Drop of the inner `T` happens after this. } } - - pub fn in_thread_mode() -> bool { - #[cfg(feature = "std")] - return Some("main") == std::thread::current().name(); - - #[cfg(not(feature = "std"))] - return cortex_m::peripheral::SCB::vect_active() - == cortex_m::peripheral::scb::VectActive::ThreadMode; - } -} - -/// A "mutex" that does nothing and cannot be shared between threads. -pub struct NoopMutex { - inner: T, -} - -impl NoopMutex { - pub const fn new(value: T) -> Self { - NoopMutex { inner: value } - } -} - -impl NoopMutex { - pub fn borrow(&self) -> &T { - &self.inner - } -} - -impl Mutex for NoopMutex { - type Data = T; - - fn new(data: T) -> Self { - Self::new(data) - } - - fn lock(&self, f: impl FnOnce(&Self::Data) -> R) -> R { - f(self.borrow()) - } } diff --git a/embassy/src/blocking_mutex/raw.rs b/embassy/src/blocking_mutex/raw.rs new file mode 100644 index 00000000..ebeb6dcc --- /dev/null +++ b/embassy/src/blocking_mutex/raw.rs @@ -0,0 +1,112 @@ +use core::marker::PhantomData; + +pub trait RawMutex { + const INIT: Self; + + fn lock(&self, f: impl FnOnce() -> R) -> R; +} + +pub struct CriticalSectionRawMutex { + _phantom: PhantomData<()>, +} +unsafe impl Send for CriticalSectionRawMutex {} +unsafe impl Sync for CriticalSectionRawMutex {} + +impl CriticalSectionRawMutex { + pub const fn new() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl RawMutex for CriticalSectionRawMutex { + const INIT: Self = Self::new(); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + critical_section::with(|_| f()) + } +} + +// ================ + +pub struct NoopRawMutex { + _phantom: PhantomData<*mut ()>, +} + +unsafe impl Send for NoopRawMutex {} + +impl NoopRawMutex { + pub const fn new() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl RawMutex for NoopRawMutex { + const INIT: Self = Self::new(); + fn lock(&self, f: impl FnOnce() -> R) -> R { + f() + } +} + +// ================ + +#[cfg(any(cortex_m, feature = "std"))] +mod thread_mode { + use super::*; + + pub struct ThreadModeRawMutex { + _phantom: PhantomData<()>, + } + + unsafe impl Send for ThreadModeRawMutex {} + unsafe impl Sync for ThreadModeRawMutex {} + + impl ThreadModeRawMutex { + pub const fn new() -> Self { + Self { + _phantom: PhantomData, + } + } + } + + impl RawMutex for ThreadModeRawMutex { + const INIT: Self = Self::new(); + fn lock(&self, f: impl FnOnce() -> R) -> R { + assert!( + in_thread_mode(), + "ThreadModeMutex can only be locked from thread mode." + ); + + f() + } + } + + impl Drop for ThreadModeRawMutex { + fn drop(&mut self) { + // Only allow dropping from thread mode. Dropping calls drop on the inner `T`, so + // `drop` needs the same guarantees as `lock`. `ThreadModeMutex` is Send even if + // T isn't, so without this check a user could create a ThreadModeMutex in thread mode, + // send it to interrupt context and drop it there, which would "send" a T even if T is not Send. + assert!( + in_thread_mode(), + "ThreadModeMutex can only be dropped from thread mode." + ); + + // Drop of the inner `T` happens after this. + } + } + + pub(crate) fn in_thread_mode() -> bool { + #[cfg(feature = "std")] + return Some("main") == std::thread::current().name(); + + #[cfg(not(feature = "std"))] + return cortex_m::peripheral::SCB::vect_active() + == cortex_m::peripheral::scb::VectActive::ThreadMode; + } +} +#[cfg(any(cortex_m, feature = "std"))] +pub use thread_mode::*; diff --git a/embassy/src/channel/mpsc.rs b/embassy/src/channel/mpsc.rs index 04709cb9..32787d81 100644 --- a/embassy/src/channel/mpsc.rs +++ b/embassy/src/channel/mpsc.rs @@ -47,7 +47,7 @@ use core::task::Waker; use futures::Future; use heapless::Deque; -use crate::blocking_mutex::kind::MutexKind; +use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; use crate::waitqueue::WakerRegistration; @@ -56,7 +56,7 @@ use crate::waitqueue::WakerRegistration; /// Instances are created by the [`split`](split) function. pub struct Sender<'ch, M, T, const N: usize> where - M: MutexKind, + M: RawMutex, { channel: &'ch Channel, } @@ -66,7 +66,7 @@ where /// Instances are created by the [`split`](split) function. pub struct Receiver<'ch, M, T, const N: usize> where - M: MutexKind, + M: RawMutex, { channel: &'ch Channel, } @@ -99,7 +99,7 @@ pub fn split( channel: &mut Channel, ) -> (Sender, Receiver) where - M: MutexKind, + M: RawMutex, { let sender = Sender { channel }; let receiver = Receiver { channel }; @@ -112,7 +112,7 @@ where impl<'ch, M, T, const N: usize> Receiver<'ch, M, T, N> where - M: MutexKind, + M: RawMutex, { /// Receives the next value for this receiver. /// @@ -161,7 +161,7 @@ where impl<'ch, M, T, const N: usize> Drop for Receiver<'ch, M, T, N> where - M: MutexKind, + M: RawMutex, { fn drop(&mut self) { self.channel.lock(|c| c.deregister_receiver()) @@ -170,14 +170,14 @@ where pub struct RecvFuture<'ch, M, T, const N: usize> where - M: MutexKind, + M: RawMutex, { channel: &'ch Channel, } impl<'ch, M, T, const N: usize> Future for RecvFuture<'ch, M, T, N> where - M: MutexKind, + M: RawMutex, { type Output = Option; @@ -193,7 +193,7 @@ where impl<'ch, M, T, const N: usize> Sender<'ch, M, T, N> where - M: MutexKind, + M: RawMutex, { /// Sends a value, waiting until there is capacity. /// @@ -268,7 +268,7 @@ where pub struct SendFuture<'ch, M, T, const N: usize> where - M: MutexKind, + M: RawMutex, { channel: &'ch Channel, message: Option, @@ -276,7 +276,7 @@ where impl<'ch, M, T, const N: usize> Future for SendFuture<'ch, M, T, N> where - M: MutexKind, + M: RawMutex, { type Output = Result<(), SendError>; @@ -295,18 +295,18 @@ where } } -impl<'ch, M, T, const N: usize> Unpin for SendFuture<'ch, M, T, N> where M: MutexKind {} +impl<'ch, M, T, const N: usize> Unpin for SendFuture<'ch, M, T, N> where M: RawMutex {} struct CloseFuture<'ch, M, T, const N: usize> where - M: MutexKind, + M: RawMutex, { channel: &'ch Channel, } impl<'ch, M, T, const N: usize> Future for CloseFuture<'ch, M, T, N> where - M: MutexKind, + M: RawMutex, { type Output = (); @@ -321,7 +321,7 @@ where impl<'ch, M, T, const N: usize> Drop for Sender<'ch, M, T, N> where - M: MutexKind, + M: RawMutex, { fn drop(&mut self) { self.channel.lock(|c| c.deregister_sender()) @@ -330,7 +330,7 @@ where impl<'ch, M, T, const N: usize> Clone for Sender<'ch, M, T, N> where - M: MutexKind, + M: RawMutex, { fn clone(&self) -> Self { self.channel.lock(|c| c.register_sender()); @@ -546,30 +546,50 @@ impl ChannelState { /// All data sent will become available in the same order as it was sent. pub struct Channel where - M: MutexKind, + M: RawMutex, { - inner: M::Mutex>>, + inner: Mutex>>, } impl Channel where - M: MutexKind, + M: RawMutex, { /// Establish a new bounded channel. For example, to create one with a NoopMutex: /// /// ``` /// use embassy::channel::mpsc; - /// use embassy::blocking_mutex::kind::Noop; + /// use embassy::blocking_mutex::raw::NoopRawMutex; /// use embassy::channel::mpsc::Channel; /// /// // Declare a bounded channel of 3 u32s. - /// let mut channel = Channel::::new(); + /// let mut channel = Channel::::new(); /// // once we have a channel, obtain its sender and receiver /// let (sender, receiver) = mpsc::split(&mut channel); /// ``` + #[cfg(feature = "nightly")] + pub const fn new() -> Self { + Self { + inner: Mutex::new(RefCell::new(ChannelState::new())), + } + } + + /// Establish a new bounded channel. For example, to create one with a NoopMutex: + /// + /// ``` + /// use embassy::channel::mpsc; + /// use embassy::blocking_mutex::raw::NoopRawMutex; + /// use embassy::channel::mpsc::Channel; + /// + /// // Declare a bounded channel of 3 u32s. + /// let mut channel = Channel::::new(); + /// // once we have a channel, obtain its sender and receiver + /// let (sender, receiver) = mpsc::split(&mut channel); + /// ``` + #[cfg(not(feature = "nightly"))] pub fn new() -> Self { Self { - inner: M::Mutex::new(RefCell::new(ChannelState::new())), + inner: Mutex::new(RefCell::new(ChannelState::new())), } } @@ -586,7 +606,7 @@ mod tests { use futures_executor::ThreadPool; use futures_timer::Delay; - use crate::blocking_mutex::kind::{CriticalSection, Noop}; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; use crate::util::Forever; use super::*; @@ -655,7 +675,7 @@ mod tests { #[test] fn simple_send_and_receive() { - let mut c = Channel::::new(); + let mut c = Channel::::new(); let (s, r) = split(&mut c); assert!(s.clone().try_send(1).is_ok()); assert_eq!(r.try_recv().unwrap(), 1); @@ -663,7 +683,7 @@ mod tests { #[test] fn should_close_without_sender() { - let mut c = Channel::::new(); + let mut c = Channel::::new(); let (s, r) = split(&mut c); drop(s); match r.try_recv() { @@ -674,7 +694,7 @@ mod tests { #[test] fn should_close_once_drained() { - let mut c = Channel::::new(); + let mut c = Channel::::new(); let (s, r) = split(&mut c); assert!(s.try_send(1).is_ok()); drop(s); @@ -687,7 +707,7 @@ mod tests { #[test] fn should_reject_send_when_receiver_dropped() { - let mut c = Channel::::new(); + let mut c = Channel::::new(); let (s, r) = split(&mut c); drop(r); match s.try_send(1) { @@ -698,7 +718,7 @@ mod tests { #[test] fn should_reject_send_when_channel_closed() { - let mut c = Channel::::new(); + let mut c = Channel::::new(); let (s, mut r) = split(&mut c); assert!(s.try_send(1).is_ok()); r.close(); @@ -714,7 +734,7 @@ mod tests { async fn receiver_closes_when_sender_dropped_async() { let executor = ThreadPool::new().unwrap(); - static CHANNEL: Forever> = Forever::new(); + static CHANNEL: Forever> = Forever::new(); let c = CHANNEL.put(Channel::new()); let (s, mut r) = split(c); assert!(executor @@ -729,7 +749,7 @@ mod tests { async fn receiver_receives_given_try_send_async() { let executor = ThreadPool::new().unwrap(); - static CHANNEL: Forever> = Forever::new(); + static CHANNEL: Forever> = Forever::new(); let c = CHANNEL.put(Channel::new()); let (s, mut r) = split(c); assert!(executor @@ -742,7 +762,7 @@ mod tests { #[futures_test::test] async fn sender_send_completes_if_capacity() { - let mut c = Channel::::new(); + let mut c = Channel::::new(); let (s, mut r) = split(&mut c); assert!(s.send(1).await.is_ok()); assert_eq!(r.recv().await, Some(1)); @@ -750,7 +770,7 @@ mod tests { #[futures_test::test] async fn sender_send_completes_if_closed() { - static CHANNEL: Forever> = Forever::new(); + static CHANNEL: Forever> = Forever::new(); let c = CHANNEL.put(Channel::new()); let (s, r) = split(c); drop(r); @@ -764,7 +784,7 @@ mod tests { async fn senders_sends_wait_until_capacity() { let executor = ThreadPool::new().unwrap(); - static CHANNEL: Forever> = Forever::new(); + static CHANNEL: Forever> = Forever::new(); let c = CHANNEL.put(Channel::new()); let (s0, mut r) = split(c); assert!(s0.try_send(1).is_ok()); @@ -784,7 +804,7 @@ mod tests { #[futures_test::test] async fn sender_close_completes_if_closing() { - static CHANNEL: Forever> = Forever::new(); + static CHANNEL: Forever> = Forever::new(); let c = CHANNEL.put(Channel::new()); let (s, mut r) = split(c); r.close(); @@ -793,7 +813,7 @@ mod tests { #[futures_test::test] async fn sender_close_completes_if_closed() { - static CHANNEL: Forever> = Forever::new(); + static CHANNEL: Forever> = Forever::new(); let c = CHANNEL.put(Channel::new()); let (s, r) = split(c); drop(r); diff --git a/embassy/src/channel/signal.rs b/embassy/src/channel/signal.rs index 87922b2f..027f4f47 100644 --- a/embassy/src/channel/signal.rs +++ b/embassy/src/channel/signal.rs @@ -32,13 +32,15 @@ enum State { unsafe impl Send for Signal {} unsafe impl Sync for Signal {} -impl Signal { +impl Signal { pub const fn new() -> Self { Self { state: UnsafeCell::new(State::None), } } +} +impl Signal { /// Mark this Signal as completed. pub fn signal(&self, val: T) { critical_section::with(|_| unsafe { diff --git a/embassy/src/executor/raw/mod.rs b/embassy/src/executor/raw/mod.rs index 5ee38715..3ae52ae3 100644 --- a/embassy/src/executor/raw/mod.rs +++ b/embassy/src/executor/raw/mod.rs @@ -57,6 +57,7 @@ pub struct TaskHeader { } impl TaskHeader { + #[cfg(feature = "nightly")] pub(crate) const fn new() -> Self { Self { state: AtomicU32::new(0), @@ -71,6 +72,21 @@ impl TaskHeader { } } + #[cfg(not(feature = "nightly"))] + pub(crate) fn new() -> Self { + Self { + state: AtomicU32::new(0), + run_queue_item: RunQueueItem::new(), + executor: Cell::new(ptr::null()), + poll_fn: UninitCell::uninit(), + + #[cfg(feature = "time")] + expires_at: Cell::new(Instant::from_ticks(0)), + #[cfg(feature = "time")] + timer_queue_item: timer_queue::TimerQueueItem::new(), + } + } + pub(crate) unsafe fn enqueue(&self) { critical_section::with(|cs| { let state = self.state.load(Ordering::Relaxed); @@ -113,7 +129,8 @@ pub struct TaskStorage { } impl TaskStorage { - /// Create a new Task, in not-spawned state. + /// Create a new TaskStorage, in not-spawned state. + #[cfg(feature = "nightly")] pub const fn new() -> Self { Self { raw: TaskHeader::new(), @@ -121,6 +138,15 @@ impl TaskStorage { } } + /// Create a new TaskStorage, in not-spawned state. + #[cfg(not(feature = "nightly"))] + pub fn new() -> Self { + Self { + raw: TaskHeader::new(), + future: UninitCell::uninit(), + } + } + /// Try to spawn a task in a pool. /// /// See [`Self::spawn()`] for details. diff --git a/embassy/src/lib.rs b/embassy/src/lib.rs index 2be0e005..acc71e10 100644 --- a/embassy/src/lib.rs +++ b/embassy/src/lib.rs @@ -1,8 +1,13 @@ #![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)] -#![feature(generic_associated_types)] -#![feature(const_fn_trait_bound)] -#![feature(const_fn_fn_ptr_basics)] -#![feature(type_alias_impl_trait)] +#![cfg_attr( + feature = "nightly", + feature( + const_fn_trait_bound, + const_fn_fn_ptr_basics, + generic_associated_types, + type_alias_impl_trait + ) +)] #![allow(clippy::new_without_default)] // This mod MUST go first, so that the others see its macros. @@ -20,7 +25,8 @@ pub mod io; pub mod time; pub mod util; -pub use embassy_macros::*; +#[cfg(feature = "nightly")] +pub use embassy_macros::{main, task}; #[doc(hidden)] /// Implementation details for embassy macros. DO NOT USE. diff --git a/embassy/src/time/delay.rs b/embassy/src/time/delay.rs index ff32941e..27ec61fe 100644 --- a/embassy/src/time/delay.rs +++ b/embassy/src/time/delay.rs @@ -16,11 +16,7 @@ pub struct Delay; #[cfg(feature = "unstable-traits")] mod eh1 { - use core::future::Future; - use futures::FutureExt; - use super::*; - use crate::time::Timer; impl embedded_hal_1::delay::blocking::DelayUs for Delay { type Error = core::convert::Infallible; @@ -33,6 +29,14 @@ mod eh1 { Ok(block_for(Duration::from_millis(ms as u64))) } } +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eh1a { + use super::*; + use crate::time::Timer; + use core::future::Future; + use futures::FutureExt; impl embedded_hal_async::delay::DelayUs for Delay { type Error = core::convert::Infallible; diff --git a/embassy/src/waitqueue/waker_agnostic.rs b/embassy/src/waitqueue/waker_agnostic.rs index f583fa6f..89430aa4 100644 --- a/embassy/src/waitqueue/waker_agnostic.rs +++ b/embassy/src/waitqueue/waker_agnostic.rs @@ -2,7 +2,8 @@ use core::cell::Cell; use core::mem; use core::task::Waker; -use crate::blocking_mutex::CriticalSectionMutex as Mutex; +use crate::blocking_mutex::raw::CriticalSectionRawMutex; +use crate::blocking_mutex::Mutex; /// Utility struct to register and wake a waker. #[derive(Debug)] @@ -50,13 +51,13 @@ impl WakerRegistration { /// Utility struct to register and wake a waker. pub struct AtomicWaker { - waker: Mutex>>, + waker: Mutex>>, } impl AtomicWaker { pub const fn new() -> Self { Self { - waker: Mutex::new(Cell::new(None)), + waker: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), } } diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index da16bcba..2d9c9953 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -4,6 +4,9 @@ edition = "2018" name = "embassy-nrf-examples" version = "0.1.0" +[features] +default = ["nightly"] +nightly = ["embassy-nrf/nightly"] [dependencies] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt"] } diff --git a/examples/nrf/src/bin/mpsc.rs b/examples/nrf/src/bin/mpsc.rs index 454fb954..d50736d8 100644 --- a/examples/nrf/src/bin/mpsc.rs +++ b/examples/nrf/src/bin/mpsc.rs @@ -6,7 +6,7 @@ mod example_common; use defmt::unwrap; -use embassy::blocking_mutex::kind::Noop; +use embassy::blocking_mutex::raw::NoopRawMutex; use embassy::channel::mpsc::{self, Channel, Sender, TryRecvError}; use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; @@ -19,10 +19,10 @@ enum LedState { Off, } -static CHANNEL: Forever> = Forever::new(); +static CHANNEL: Forever> = Forever::new(); #[embassy::task(pool_size = 1)] -async fn my_task(sender: Sender<'static, Noop, LedState, 1>) { +async fn my_task(sender: Sender<'static, NoopRawMutex, LedState, 1>) { loop { let _ = sender.send(LedState::On).await; Timer::after(Duration::from_secs(1)).await; diff --git a/examples/nrf/src/bin/uart_split.rs b/examples/nrf/src/bin/uart_split.rs index 75079837..19d438c4 100644 --- a/examples/nrf/src/bin/uart_split.rs +++ b/examples/nrf/src/bin/uart_split.rs @@ -6,7 +6,7 @@ mod example_common; use example_common::*; -use embassy::blocking_mutex::kind::Noop; +use embassy::blocking_mutex::raw::NoopRawMutex; use embassy::channel::mpsc::{self, Channel, Sender}; use embassy::executor::Spawner; use embassy::util::Forever; @@ -14,7 +14,7 @@ use embassy_nrf::peripherals::UARTE0; use embassy_nrf::uarte::UarteRx; use embassy_nrf::{interrupt, uarte, Peripherals}; -static CHANNEL: Forever> = Forever::new(); +static CHANNEL: Forever> = Forever::new(); #[embassy::main] async fn main(spawner: Spawner, p: Peripherals) { @@ -56,7 +56,7 @@ async fn main(spawner: Spawner, p: Peripherals) { } #[embassy::task] -async fn reader(mut rx: UarteRx<'static, UARTE0>, s: Sender<'static, Noop, [u8; 8], 1>) { +async fn reader(mut rx: UarteRx<'static, UARTE0>, s: Sender<'static, NoopRawMutex, [u8; 8], 1>) { let mut buf = [0; 8]; loop { info!("reading..."); diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 450911fa..ef60fe99 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -5,7 +5,7 @@ name = "embassy-std-examples" version = "0.1.0" [dependencies] -embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "std", "time"] } +embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "std", "time", "nightly"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features=["std", "log", "medium-ethernet", "tcp", "dhcpv4"] } async-io = "1.6.0" diff --git a/examples/stm32f3/src/bin/button_events.rs b/examples/stm32f3/src/bin/button_events.rs index 720ed9d1..1218edd2 100644 --- a/examples/stm32f3/src/bin/button_events.rs +++ b/examples/stm32f3/src/bin/button_events.rs @@ -12,7 +12,7 @@ #[path = "../example_common.rs"] mod example_common; -use embassy::blocking_mutex::kind::Noop; +use embassy::blocking_mutex::raw::NoopRawMutex; use embassy::channel::mpsc::{self, Channel, Receiver, Sender}; use embassy::executor::Spawner; use embassy::time::{with_timeout, Duration, Timer}; @@ -77,7 +77,7 @@ enum ButtonEvent { Hold, } -static BUTTON_EVENTS_QUEUE: Forever> = Forever::new(); +static BUTTON_EVENTS_QUEUE: Forever> = Forever::new(); #[embassy::main] async fn main(spawner: Spawner, p: Peripherals) { @@ -103,7 +103,10 @@ async fn main(spawner: Spawner, p: Peripherals) { } #[embassy::task] -async fn led_blinker(mut leds: Leds<'static>, queue: Receiver<'static, Noop, ButtonEvent, 4>) { +async fn led_blinker( + mut leds: Leds<'static>, + queue: Receiver<'static, NoopRawMutex, ButtonEvent, 4>, +) { loop { leds.blink().await; match queue.try_recv() { @@ -121,7 +124,7 @@ async fn led_blinker(mut leds: Leds<'static>, queue: Receiver<'static, Noop, But #[embassy::task] async fn button_waiter( mut button: ExtiInput<'static, PA0>, - queue: Sender<'static, Noop, ButtonEvent, 4>, + queue: Sender<'static, NoopRawMutex, ButtonEvent, 4>, ) { const DOUBLE_CLICK_DELAY: u64 = 250; const HOLD_DELAY: u64 = 1000; diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index c6218a80..6750f6a6 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -8,7 +8,7 @@ version = "0.1.0" crate-type = ["cdylib"] [dependencies] -embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "wasm"] } +embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "wasm", "nightly"] } wasm-logger = "0.2.0" wasm-bindgen = "0.2"