diff --git a/Cargo.example.toml b/Cargo.example.toml index f072c2f9..0e9d1e32 100644 --- a/Cargo.example.toml +++ b/Cargo.example.toml @@ -45,7 +45,6 @@ members = [ #"examples/rp", # std - #"embassy-std", #"examples/std", ] diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml index ec76299e..0ff71ce1 100644 --- a/embassy/Cargo.toml +++ b/embassy/Cargo.toml @@ -7,7 +7,7 @@ resolver = "2" [features] default = [] -std = ["futures/std", "embassy-traits/std"] +std = ["futures/std", "embassy-traits/std", "time", "time-tick-1mhz"] # 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/executor/mod.rs b/embassy/src/executor/mod.rs index e0ac566f..5ffbe689 100644 --- a/embassy/src/executor/mod.rs +++ b/embassy/src/executor/mod.rs @@ -1,4 +1,5 @@ -#[path = "arch/arm.rs"] +#[cfg_attr(feature = "std", path = "arch/std.rs")] +#[cfg_attr(not(feature = "std"), path = "arch/arm.rs")] mod arch; pub mod raw; mod spawner; diff --git a/embassy/src/time/driver_std.rs b/embassy/src/time/driver_std.rs new file mode 100644 index 00000000..29911c4d --- /dev/null +++ b/embassy/src/time/driver_std.rs @@ -0,0 +1,208 @@ +use atomic_polyfill::{AtomicU8, Ordering}; +use std::cell::UnsafeCell; +use std::mem; +use std::mem::MaybeUninit; +use std::sync::{Condvar, Mutex, Once}; +use std::time::Duration as StdDuration; +use std::time::Instant as StdInstant; +use std::{ptr, thread}; + +use crate::time::driver::{AlarmHandle, Driver}; + +const ALARM_COUNT: usize = 4; + +struct AlarmState { + timestamp: u64, + + // This is really a Option<(fn(*mut ()), *mut ())> + // but fn pointers aren't allowed in const yet + callback: *const (), + ctx: *mut (), +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: u64::MAX, + callback: ptr::null(), + ctx: ptr::null_mut(), + } + } +} + +struct TimeDriver { + alarm_count: AtomicU8, + + once: Once, + alarms: UninitCell>, + zero_instant: UninitCell, + signaler: UninitCell, +} + +const ALARM_NEW: AlarmState = AlarmState::new(); +crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { + alarm_count: AtomicU8::new(0), + + once: Once::new(), + alarms: UninitCell::uninit(), + zero_instant: UninitCell::uninit(), + signaler: UninitCell::uninit(), +}); + +impl TimeDriver { + fn init(&self) { + self.once.call_once(|| unsafe { + self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); + self.zero_instant.write(StdInstant::now()); + self.signaler.write(Signaler::new()); + + thread::spawn(Self::alarm_thread); + }); + } + + fn alarm_thread() { + loop { + let now = DRIVER.now(); + + let mut next_alarm = u64::MAX; + { + let alarms = &mut *unsafe { DRIVER.alarms.as_ref() }.lock().unwrap(); + for alarm in alarms { + if alarm.timestamp <= now { + alarm.timestamp = u64::MAX; + + // Call after clearing alarm, so the callback can set another alarm. + + // safety: + // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. + // - other than that we only store valid function pointers into alarm.callback + let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback) }; + f(alarm.ctx); + } else { + next_alarm = next_alarm.min(alarm.timestamp); + } + } + } + + let until = + unsafe { DRIVER.zero_instant.read() } + StdDuration::from_micros(next_alarm); + + unsafe { DRIVER.signaler.as_ref() }.wait_until(until); + } + } +} + +impl Driver for TimeDriver { + fn now(&self) -> u64 { + self.init(); + + let zero = unsafe { self.zero_instant.read() }; + StdInstant::now().duration_since(zero).as_micros() as u64 + } + + unsafe fn allocate_alarm(&self) -> Option { + let id = self + .alarm_count + .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { + if x < ALARM_COUNT as u8 { + Some(x + 1) + } else { + None + } + }); + + match id { + Ok(id) => Some(AlarmHandle::new(id)), + Err(_) => None, + } + } + + fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { + self.init(); + let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.callback = callback as *const (); + alarm.ctx = ctx; + } + + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + self.init(); + let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.timestamp = timestamp; + unsafe { self.signaler.as_ref() }.signal(); + } +} + +struct Signaler { + mutex: Mutex, + condvar: Condvar, +} + +impl Signaler { + fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait_until(&self, until: StdInstant) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + let now = StdInstant::now(); + + if now >= until { + break; + } + + let dur = until - now; + let (signaled2, timeout) = self.condvar.wait_timeout(signaled, dur).unwrap(); + signaled = signaled2; + if timeout.timed_out() { + break; + } + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } +} + +pub(crate) struct UninitCell(MaybeUninit>); +unsafe impl Send for UninitCell {} +unsafe impl Sync for UninitCell {} + +impl UninitCell { + pub const fn uninit() -> Self { + Self(MaybeUninit::uninit()) + } + + pub unsafe fn as_ptr(&self) -> *const T { + (*self.0.as_ptr()).get() + } + + pub unsafe fn as_mut_ptr(&self) -> *mut T { + (*self.0.as_ptr()).get() + } + + pub unsafe fn as_ref(&self) -> &T { + &*self.as_ptr() + } + + pub unsafe fn write(&self, val: T) { + ptr::write(self.as_mut_ptr(), val) + } +} + +impl UninitCell { + pub unsafe fn read(&self) -> T { + ptr::read(self.as_mut_ptr()) + } +} diff --git a/embassy/src/time/mod.rs b/embassy/src/time/mod.rs index db48fb3e..e5074204 100644 --- a/embassy/src/time/mod.rs +++ b/embassy/src/time/mod.rs @@ -46,6 +46,9 @@ mod duration; mod instant; mod timer; +#[cfg(feature = "std")] +mod driver_std; + pub use delay::{block_for, Delay}; pub use duration::Duration; pub use instant::Instant; diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 46681485..34b8ebb6 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -5,8 +5,7 @@ name = "embassy-std-examples" version = "0.1.0" [dependencies] -embassy = { version = "0.1.0", path = "../../embassy", features = ["log"] } -embassy-std = { version = "0.1.0", path = "../../embassy-std" } +embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "std", "time"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features=["std", "log", "medium-ethernet", "tcp", "dhcpv4"] } smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", rev="e4241510337e095b9d21136c5f58b2eaa1b78479", default-features = false } diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index 323d711a..b98b9709 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs @@ -2,11 +2,10 @@ #![allow(incomplete_features)] use clap::{AppSettings, Clap}; -use embassy::executor::Spawner; +use embassy::executor::{Executor, Spawner}; use embassy::io::AsyncWriteExt; use embassy::util::Forever; use embassy_net::*; -use embassy_std::Executor; use heapless::Vec; use log::*; diff --git a/examples/std/src/bin/serial.rs b/examples/std/src/bin/serial.rs index ca596a34..181c5dfa 100644 --- a/examples/std/src/bin/serial.rs +++ b/examples/std/src/bin/serial.rs @@ -5,9 +5,9 @@ mod serial_port; use async_io::Async; +use embassy::executor::Executor; use embassy::io::AsyncBufReadExt; use embassy::util::Forever; -use embassy_std::Executor; use log::*; use nix::sys::termios; diff --git a/examples/std/src/bin/tick.rs b/examples/std/src/bin/tick.rs index 16f54b2c..385b317d 100644 --- a/examples/std/src/bin/tick.rs +++ b/examples/std/src/bin/tick.rs @@ -1,9 +1,9 @@ #![feature(type_alias_impl_trait)] #![allow(incomplete_features)] +use embassy::executor::Executor; use embassy::time::{Duration, Timer}; use embassy::util::Forever; -use embassy_std::Executor; use log::*; #[embassy::task]