From f1c55bf785f5623735de5b238b384f1ab7c7b21f Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 14:40:43 +0100 Subject: [PATCH 01/13] riot-rs-embassy: drop InitializationArgs --- src/riot-rs-embassy/src/lib.rs | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/riot-rs-embassy/src/lib.rs b/src/riot-rs-embassy/src/lib.rs index 5815b6ac8..96b1c6cad 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -23,31 +23,21 @@ pub mod define_peripherals; )] pub mod arch; +#[cfg(feature = "net")] use core::cell::OnceCell; // re-exports pub use linkme::{self, distributed_slice}; pub use static_cell::make_static; -use embassy_executor::Spawner; -use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; - use crate::define_peripherals::DefinePeripheralsError; +use embassy_executor::Spawner; #[cfg(feature = "threading")] pub mod blocker; -pub type Task = fn( - &mut arch::OptionalPeripherals, - InitializationArgs, -) -> Result<&dyn Application, ApplicationInitError>; - -// Allows us to pass extra initialization arguments in the future -#[derive(Copy, Clone)] -#[non_exhaustive] -pub struct InitializationArgs { - pub peripherals: &'static Mutex, -} +pub type Task = + fn(&mut arch::OptionalPeripherals) -> Result<&dyn Application, ApplicationInitError>; #[cfg(feature = "usb")] pub type UsbBuilder = embassy_usb::Builder<'static, UsbDriver>; @@ -313,13 +303,9 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { wifi::join(control).await; }; - let init_args = InitializationArgs { - peripherals: make_static!(Mutex::new(peripherals)), - }; - for task in EMBASSY_TASKS { // TODO: should all tasks be initialized before starting the first one? - match task(&mut *init_args.peripherals.lock().await, init_args) { + match task(&mut peripherals) { Ok(initialized_application) => initialized_application.start(spawner, drivers), Err(err) => panic!("Error while initializing an application: {err:?}"), } @@ -343,7 +329,6 @@ pub trait Application { /// The [`assign_resources!`] macro can be leveraged to extract the required peripherals. fn initialize( peripherals: &mut arch::OptionalPeripherals, - init_args: InitializationArgs, ) -> Result<&dyn Application, ApplicationInitError> where Self: Sized; @@ -382,9 +367,8 @@ macro_rules! riot_initialize { #[linkme(crate = $crate::linkme)] fn __init_application( peripherals: &mut $crate::arch::OptionalPeripherals, - init_args: $crate::InitializationArgs, ) -> Result<&dyn $crate::Application, $crate::ApplicationInitError> { - <$prog_type as Application>::initialize(peripherals, init_args) + <$prog_type as Application>::initialize(peripherals) } }; } From 4ab7d9a6c9a855d55c72147270595948fa731d46 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 14:41:08 +0100 Subject: [PATCH 02/13] examples/*: drop use of InitializationArgs --- examples/application/src/main.rs | 3 +-- examples/embassy-http-server/src/main.rs | 4 +--- examples/embassy-net-tcp/src/main.rs | 3 +-- examples/embassy-net-udp/src/main.rs | 3 +-- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index f5e3bfcee..b5c97313e 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -3,7 +3,7 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers, InitializationArgs}; +use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers}; use riot_rs::rt::debug::println; @@ -14,7 +14,6 @@ struct MyApplication { impl Application for MyApplication { fn initialize( _peripherals: &mut arch::OptionalPeripherals, - _init_args: InitializationArgs, ) -> Result<&dyn Application, ApplicationInitError> { println!("MyApplication::initialize()"); Ok(&Self { state: 0 }) diff --git a/examples/embassy-http-server/src/main.rs b/examples/embassy-http-server/src/main.rs index aa53bd713..ca510caca 100644 --- a/examples/embassy-http-server/src/main.rs +++ b/examples/embassy-http-server/src/main.rs @@ -9,8 +9,7 @@ mod routes; use riot_rs as _; use riot_rs::embassy::{ - arch::OptionalPeripherals, Application, ApplicationInitError, Drivers, InitializationArgs, - NetworkStack, + arch::OptionalPeripherals, Application, ApplicationInitError, Drivers, NetworkStack, }; use riot_rs::rt::debug::println; @@ -95,7 +94,6 @@ struct WebServer { impl Application for WebServer { fn initialize( peripherals: &mut OptionalPeripherals, - _init_args: InitializationArgs, ) -> Result<&dyn Application, ApplicationInitError> { #[cfg(feature = "button-readings")] let button_inputs = { diff --git a/examples/embassy-net-tcp/src/main.rs b/examples/embassy-net-tcp/src/main.rs index c768d9272..08da16e13 100644 --- a/examples/embassy-net-tcp/src/main.rs +++ b/examples/embassy-net-tcp/src/main.rs @@ -3,7 +3,7 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers, InitializationArgs}; +use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers}; use riot_rs::rt::debug::println; @@ -61,7 +61,6 @@ struct TcpEcho {} impl Application for TcpEcho { fn initialize( _peripherals: &mut arch::OptionalPeripherals, - _init_args: InitializationArgs, ) -> Result<&dyn Application, ApplicationInitError> { Ok(&Self {}) } diff --git a/examples/embassy-net-udp/src/main.rs b/examples/embassy-net-udp/src/main.rs index 3a0e6443a..f8d33f9f8 100644 --- a/examples/embassy-net-udp/src/main.rs +++ b/examples/embassy-net-udp/src/main.rs @@ -3,7 +3,7 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers, InitializationArgs}; +use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers}; use riot_rs::rt::debug::println; @@ -66,7 +66,6 @@ struct UdpEcho {} impl Application for UdpEcho { fn initialize( _peripherals: &mut arch::OptionalPeripherals, - _init_args: InitializationArgs, ) -> Result<&dyn Application, ApplicationInitError> { Ok(&Self {}) } From 7df69481f26b6723b5cf664fef885b845c8fc68f Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Wed, 14 Feb 2024 21:43:10 +0100 Subject: [PATCH 03/13] riot-rs-embassy: introduce "SendCell" --- src/riot-rs-embassy/src/lib.rs | 1 + src/riot-rs-embassy/src/sendcell.rs | 46 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/riot-rs-embassy/src/sendcell.rs diff --git a/src/riot-rs-embassy/src/lib.rs b/src/riot-rs-embassy/src/lib.rs index 96b1c6cad..23fe574a3 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -35,6 +35,7 @@ use embassy_executor::Spawner; #[cfg(feature = "threading")] pub mod blocker; +pub mod sendcell; pub type Task = fn(&mut arch::OptionalPeripherals) -> Result<&dyn Application, ApplicationInitError>; diff --git a/src/riot-rs-embassy/src/sendcell.rs b/src/riot-rs-embassy/src/sendcell.rs new file mode 100644 index 000000000..3c78177e5 --- /dev/null +++ b/src/riot-rs-embassy/src/sendcell.rs @@ -0,0 +1,46 @@ +//! Pass non-Send objects around on same executor +//! +//! This module provides `SendCell`, a structure that allows passing around +//! non-Send objects from one async task to another, if they are on the same +//! executor. This is allowed because embassy tasks are single threaded. +//! `SendCell` checks for the correct executor *at runtime*. + +use embassy_executor::Spawner; + +// SAFETY: +// SendCell guarantees at runtime that its content stays on the same embassy +// executor. Those are single threaded, so it is guaranteed that the content +// stays on the same thread. +unsafe impl Send for SendCell {} + +/// A cell that allows sending of non-Send types *if they stay on the same executor*. +/// +/// This is checked *at runtime*. +#[derive(Debug)] +pub struct SendCell { + executor_id: usize, + inner: T, +} + +impl SendCell { + /// Create a new `SendCell` + /// + /// The `spawner` argument *must* point to the current executor. + pub fn new(inner: T, spawner: &Spawner) -> Self { + Self { + executor_id: spawner.executor_id(), + inner, + } + } + + /// Get content of this `SendCell` + /// + /// The `spawner` argument *must* point to the current executor. + pub fn get(&self, spawner: &Spawner) -> Option<&T> { + if spawner.executor_id() == self.executor_id { + Some(&self.inner) + } else { + None + } + } +} From 2c56ef86b431a1d6dd9fd3a2c167ac2118f8ba43 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 15:24:40 +0100 Subject: [PATCH 04/13] riot-rs-embassy: introduce `network_stack()` --- src/riot-rs-embassy/src/lib.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/riot-rs-embassy/src/lib.rs b/src/riot-rs-embassy/src/lib.rs index 23fe574a3..9e8d3b41c 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -23,9 +23,6 @@ pub mod define_peripherals; )] pub mod arch; -#[cfg(feature = "net")] -use core::cell::OnceCell; - // re-exports pub use linkme::{self, distributed_slice}; pub use static_cell::make_static; @@ -43,9 +40,25 @@ pub type Task = #[cfg(feature = "usb")] pub type UsbBuilder = embassy_usb::Builder<'static, UsbDriver>; +#[cfg(feature = "net")] +use { + self::sendcell::SendCell, core::cell::OnceCell, + embassy_sync::blocking_mutex::CriticalSectionMutex, +}; + #[cfg(feature = "net")] pub type NetworkStack = Stack; +#[cfg(feature = "net")] +pub static STACK: CriticalSectionMutex>> = + CriticalSectionMutex::new(OnceCell::new()); + +#[cfg(feature = "net")] +pub async fn network_stack() -> Option<&'static NetworkStack> { + let spawner = Spawner::for_current_executor().await; + STACK.lock(|cell| cell.get().map(|x| *x.get(&spawner).unwrap())) +} + #[derive(Copy, Clone)] pub struct Drivers { #[cfg(feature = "net")] @@ -294,9 +307,12 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { spawner.spawn(net_task(stack)).unwrap(); - // Do nothing if a stack is already initialized, as this should not happen anyway - // TODO: should we panic instead? - let _ = drivers.stack.set(stack); + if STACK + .lock(|c| c.set(SendCell::new(stack, &spawner))) + .is_err() + { + unreachable!(); + } } #[cfg(feature = "wifi_cyw43")] From b40aea8fa6a0da98039fdf54fb4e2f569aedbda0 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 15:25:09 +0100 Subject: [PATCH 05/13] examples/*: make use of `network_stack()` --- examples/embassy-http-server/src/main.rs | 13 +++++-------- examples/embassy-net-tcp/src/main.rs | 6 +++--- examples/embassy-net-udp/src/main.rs | 6 +++--- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/examples/embassy-http-server/src/main.rs b/examples/embassy-http-server/src/main.rs index ca510caca..2908d6276 100644 --- a/examples/embassy-http-server/src/main.rs +++ b/examples/embassy-http-server/src/main.rs @@ -9,7 +9,7 @@ mod routes; use riot_rs as _; use riot_rs::embassy::{ - arch::OptionalPeripherals, Application, ApplicationInitError, Drivers, NetworkStack, + arch::OptionalPeripherals, network_stack, Application, ApplicationInitError, Drivers, }; use riot_rs::rt::debug::println; @@ -53,11 +53,12 @@ const WEB_TASK_POOL_SIZE: usize = 2; #[embassy_executor::task(pool_size = WEB_TASK_POOL_SIZE)] async fn web_task( id: usize, - stack: &'static NetworkStack, app: &'static picoserve::Router, config: &'static picoserve::Config, state: AppState, ) -> ! { + let stack = network_stack().await.unwrap(); + let mut rx_buffer = [0; 1024]; let mut tx_buffer = [0; 1024]; @@ -115,9 +116,7 @@ impl Application for WebServer { })) } - fn start(&self, spawner: embassy_executor::Spawner, drivers: Drivers) { - let stack = drivers.stack.get().unwrap(); - + fn start(&self, spawner: embassy_executor::Spawner, _drivers: Drivers) { fn make_app() -> picoserve::Router { let router = picoserve::Router::new().route("/", get(routes::index)); #[cfg(feature = "button-readings")] @@ -138,9 +137,7 @@ impl Application for WebServer { #[cfg(feature = "button-readings")] buttons: self.button_inputs, }; - spawner - .spawn(web_task(id, stack, app, config, app_state)) - .unwrap(); + spawner.spawn(web_task(id, app, config, app_state)).unwrap(); } } } diff --git a/examples/embassy-net-tcp/src/main.rs b/examples/embassy-net-tcp/src/main.rs index 08da16e13..850f63b15 100644 --- a/examples/embassy-net-tcp/src/main.rs +++ b/examples/embassy-net-tcp/src/main.rs @@ -3,16 +3,16 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers}; +use riot_rs::embassy::{arch, network_stack, Application, ApplicationInitError, Drivers}; use riot_rs::rt::debug::println; use embedded_io_async::Write; #[embassy_executor::task] -async fn tcp_echo(drivers: Drivers) { +async fn tcp_echo(_drivers: Drivers) { use embassy_net::tcp::TcpSocket; - let stack = drivers.stack.get().unwrap(); + let stack = network_stack().await.unwrap(); let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; diff --git a/examples/embassy-net-udp/src/main.rs b/examples/embassy-net-udp/src/main.rs index f8d33f9f8..181ff66a6 100644 --- a/examples/embassy-net-udp/src/main.rs +++ b/examples/embassy-net-udp/src/main.rs @@ -3,14 +3,14 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers}; +use riot_rs::embassy::{arch, network_stack, Application, ApplicationInitError, Drivers}; use riot_rs::rt::debug::println; #[embassy_executor::task] -async fn udp_echo(drivers: Drivers) { +async fn udp_echo(_drivers: Drivers) { use embassy_net::udp::{PacketMetadata, UdpSocket}; - let stack = drivers.stack.get().unwrap(); + let stack = network_stack().await.unwrap(); let mut rx_meta = [PacketMetadata::EMPTY; 16]; let mut rx_buffer = [0; 4096]; From 8eecc89bd718d0326e8b35e112794600c4e01b71 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 15:37:08 +0100 Subject: [PATCH 06/13] riot-rs-embassy: drop `struct Drivers` --- src/riot-rs-embassy/src/lib.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/riot-rs-embassy/src/lib.rs b/src/riot-rs-embassy/src/lib.rs index 9e8d3b41c..7a956599b 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -59,12 +59,6 @@ pub async fn network_stack() -> Option<&'static NetworkStack> { STACK.lock(|cell| cell.get().map(|x| *x.get(&spawner).unwrap())) } -#[derive(Copy, Clone)] -pub struct Drivers { - #[cfg(feature = "net")] - pub stack: &'static OnceCell<&'static NetworkStack>, -} - pub static EXECUTOR: arch::Executor = arch::Executor::new(); #[distributed_slice] @@ -203,11 +197,6 @@ pub(crate) fn init() { async fn init_task(mut peripherals: arch::OptionalPeripherals) { riot_rs_rt::debug::println!("riot-rs-embassy::init_task()"); - let drivers = Drivers { - #[cfg(feature = "net")] - stack: make_static!(OnceCell::new()), - }; - #[cfg(all(context = "nrf52", feature = "usb"))] { // nrf52840 @@ -323,7 +312,7 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { for task in EMBASSY_TASKS { // TODO: should all tasks be initialized before starting the first one? match task(&mut peripherals) { - Ok(initialized_application) => initialized_application.start(spawner, drivers), + Ok(initialized_application) => initialized_application.start(spawner), Err(err) => panic!("Error while initializing an application: {err:?}"), } } @@ -355,8 +344,7 @@ pub trait Application { /// /// This function must not block but may spawn [Embassy tasks](embassy_executor::task) using /// the provided [`Spawner`](embassy_executor::Spawner). - /// In addition, it is provided with the drivers initialized by the system. - fn start(&self, spawner: embassy_executor::Spawner, drivers: Drivers); + fn start(&self, spawner: embassy_executor::Spawner); } /// Represents errors that can happen during application initialization. From b335d008c087f0f14d9ab8bbf37a255656b1202f Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 15:37:35 +0100 Subject: [PATCH 07/13] examples/*: drop use of `struct Drivers` --- examples/application/src/main.rs | 4 ++-- examples/embassy-http-server/src/main.rs | 4 ++-- examples/embassy-net-tcp/src/main.rs | 8 ++++---- examples/embassy-net-udp/src/main.rs | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index b5c97313e..3ed23b8bf 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -3,7 +3,7 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers}; +use riot_rs::embassy::{arch, Application, ApplicationInitError}; use riot_rs::rt::debug::println; @@ -19,7 +19,7 @@ impl Application for MyApplication { Ok(&Self { state: 0 }) } - fn start(&self, _spawner: embassy_executor::Spawner, _drivers: Drivers) { + fn start(&self, _spawner: embassy_executor::Spawner) { println!("MyApplication::start()"); // ... } diff --git a/examples/embassy-http-server/src/main.rs b/examples/embassy-http-server/src/main.rs index 2908d6276..5377c9430 100644 --- a/examples/embassy-http-server/src/main.rs +++ b/examples/embassy-http-server/src/main.rs @@ -9,7 +9,7 @@ mod routes; use riot_rs as _; use riot_rs::embassy::{ - arch::OptionalPeripherals, network_stack, Application, ApplicationInitError, Drivers, + arch::OptionalPeripherals, network_stack, Application, ApplicationInitError, }; use riot_rs::rt::debug::println; @@ -116,7 +116,7 @@ impl Application for WebServer { })) } - fn start(&self, spawner: embassy_executor::Spawner, _drivers: Drivers) { + fn start(&self, spawner: embassy_executor::Spawner) { fn make_app() -> picoserve::Router { let router = picoserve::Router::new().route("/", get(routes::index)); #[cfg(feature = "button-readings")] diff --git a/examples/embassy-net-tcp/src/main.rs b/examples/embassy-net-tcp/src/main.rs index 850f63b15..14ff936c1 100644 --- a/examples/embassy-net-tcp/src/main.rs +++ b/examples/embassy-net-tcp/src/main.rs @@ -3,14 +3,14 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, network_stack, Application, ApplicationInitError, Drivers}; +use riot_rs::embassy::{arch, network_stack, Application, ApplicationInitError}; use riot_rs::rt::debug::println; use embedded_io_async::Write; #[embassy_executor::task] -async fn tcp_echo(_drivers: Drivers) { +async fn tcp_echo() { use embassy_net::tcp::TcpSocket; let stack = network_stack().await.unwrap(); @@ -65,8 +65,8 @@ impl Application for TcpEcho { Ok(&Self {}) } - fn start(&self, spawner: embassy_executor::Spawner, drivers: Drivers) { - spawner.spawn(tcp_echo(drivers)).unwrap(); + fn start(&self, spawner: embassy_executor::Spawner) { + spawner.spawn(tcp_echo()).unwrap(); } } diff --git a/examples/embassy-net-udp/src/main.rs b/examples/embassy-net-udp/src/main.rs index 181ff66a6..cce37c8a1 100644 --- a/examples/embassy-net-udp/src/main.rs +++ b/examples/embassy-net-udp/src/main.rs @@ -3,12 +3,12 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, network_stack, Application, ApplicationInitError, Drivers}; +use riot_rs::embassy::{arch, network_stack, Application, ApplicationInitError}; use riot_rs::rt::debug::println; #[embassy_executor::task] -async fn udp_echo(_drivers: Drivers) { +async fn udp_echo() { use embassy_net::udp::{PacketMetadata, UdpSocket}; let stack = network_stack().await.unwrap(); @@ -70,8 +70,8 @@ impl Application for UdpEcho { Ok(&Self {}) } - fn start(&self, spawner: embassy_executor::Spawner, drivers: Drivers) { - spawner.spawn(udp_echo(drivers)).unwrap(); + fn start(&self, spawner: embassy_executor::Spawner) { + spawner.spawn(udp_echo()).unwrap(); } } From 5334e956ac3517b63c1f6392e3ce7090cabcee3f Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 16:48:59 +0100 Subject: [PATCH 08/13] riot-rs-embassy: drop `Application` trait --- src/riot-rs-embassy/src/lib.rs | 66 ++-------------------------------- 1 file changed, 3 insertions(+), 63 deletions(-) diff --git a/src/riot-rs-embassy/src/lib.rs b/src/riot-rs-embassy/src/lib.rs index 7a956599b..bf8126a5f 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -28,14 +28,13 @@ pub use linkme::{self, distributed_slice}; pub use static_cell::make_static; use crate::define_peripherals::DefinePeripheralsError; -use embassy_executor::Spawner; +pub use embassy_executor::Spawner; #[cfg(feature = "threading")] pub mod blocker; pub mod sendcell; -pub type Task = - fn(&mut arch::OptionalPeripherals) -> Result<&dyn Application, ApplicationInitError>; +pub type Task = fn(&Spawner, &mut arch::OptionalPeripherals); #[cfg(feature = "usb")] pub type UsbBuilder = embassy_usb::Builder<'static, UsbDriver>; @@ -310,11 +309,7 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { }; for task in EMBASSY_TASKS { - // TODO: should all tasks be initialized before starting the first one? - match task(&mut peripherals) { - Ok(initialized_application) => initialized_application.start(spawner), - Err(err) => panic!("Error while initializing an application: {err:?}"), - } + task(&spawner, &mut peripherals); } // mark used @@ -322,58 +317,3 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { riot_rs_rt::debug::println!("riot-rs-embassy::init_task() done"); } - -/// Defines an application. -/// -/// Allows to separate its fallible initialization from its infallible running phase. -pub trait Application { - /// Applications must implement this to obtain the peripherals they require. - /// - /// This function is only run once at startup and instantiates the application. - /// No guarantee is provided regarding the order in which different applications are - /// initialized. - /// The [`assign_resources!`] macro can be leveraged to extract the required peripherals. - fn initialize( - peripherals: &mut arch::OptionalPeripherals, - ) -> Result<&dyn Application, ApplicationInitError> - where - Self: Sized; - - /// After an application has been initialized, this method is called by the system to start the - /// application. - /// - /// This function must not block but may spawn [Embassy tasks](embassy_executor::task) using - /// the provided [`Spawner`](embassy_executor::Spawner). - fn start(&self, spawner: embassy_executor::Spawner); -} - -/// Represents errors that can happen during application initialization. -#[derive(Debug)] -pub enum ApplicationInitError { - /// The application could not obtain a peripheral, most likely because it was already used by - /// another application or by the system itself. - CannotTakePeripheral, -} - -impl From for ApplicationInitError { - fn from(err: DefinePeripheralsError) -> Self { - match err { - DefinePeripheralsError::TakingPeripheral => Self::CannotTakePeripheral, - } - } -} - -/// Sets the [`Application::initialize()`] function implemented on the provided type to be run at -/// startup. -#[macro_export] -macro_rules! riot_initialize { - ($prog_type:ident) => { - #[$crate::distributed_slice($crate::EMBASSY_TASKS)] - #[linkme(crate = $crate::linkme)] - fn __init_application( - peripherals: &mut $crate::arch::OptionalPeripherals, - ) -> Result<&dyn $crate::Application, $crate::ApplicationInitError> { - <$prog_type as Application>::initialize(peripherals) - } - }; -} From b626c57a818cd66cfee06a3d75d8b7a4173cb949 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 16:49:54 +0100 Subject: [PATCH 09/13] examples/*: drop use of `Application` trait --- examples/application/Cargo.toml | 12 ---- examples/application/README.md | 5 -- examples/application/laze.yml | 4 -- examples/application/src/main.rs | 28 -------- examples/embassy-http-server/src/main.rs | 84 ++++++++++-------------- examples/embassy-net-tcp/src/main.rs | 22 ++----- examples/embassy-net-udp/src/main.rs | 22 ++----- examples/laze.yml | 1 - 8 files changed, 48 insertions(+), 130 deletions(-) delete mode 100644 examples/application/Cargo.toml delete mode 100644 examples/application/README.md delete mode 100644 examples/application/laze.yml delete mode 100644 examples/application/src/main.rs diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml deleted file mode 100644 index eb67bde58..000000000 --- a/examples/application/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "application" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -publish = false - -[dependencies] -riot-rs = { path = "../../src/riot-rs" } -riot-rs-boards = { path = "../../src/riot-rs-boards" } -embassy-executor = { workspace = true, default-features = false } -embassy-time = { workspace = true, default-features = false } diff --git a/examples/application/README.md b/examples/application/README.md deleted file mode 100644 index 3f48b44f1..000000000 --- a/examples/application/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# application - -## About - -This folder contains a minimal `Application` example. diff --git a/examples/application/laze.yml b/examples/application/laze.yml deleted file mode 100644 index 7b93f3c0c..000000000 --- a/examples/application/laze.yml +++ /dev/null @@ -1,4 +0,0 @@ -apps: - - name: application - selects: - - ?release diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs deleted file mode 100644 index 3ed23b8bf..000000000 --- a/examples/application/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![feature(used_with_arg)] - -use riot_rs::embassy::{arch, Application, ApplicationInitError}; - -use riot_rs::rt::debug::println; - -struct MyApplication { - state: u32, // some state -} - -impl Application for MyApplication { - fn initialize( - _peripherals: &mut arch::OptionalPeripherals, - ) -> Result<&dyn Application, ApplicationInitError> { - println!("MyApplication::initialize()"); - Ok(&Self { state: 0 }) - } - - fn start(&self, _spawner: embassy_executor::Spawner) { - println!("MyApplication::start()"); - // ... - } -} - -riot_rs::embassy::riot_initialize!(MyApplication); diff --git a/examples/embassy-http-server/src/main.rs b/examples/embassy-http-server/src/main.rs index 5377c9430..4a969813e 100644 --- a/examples/embassy-http-server/src/main.rs +++ b/examples/embassy-http-server/src/main.rs @@ -8,9 +8,7 @@ mod routes; use riot_rs as _; -use riot_rs::embassy::{ - arch::OptionalPeripherals, network_stack, Application, ApplicationInitError, -}; +use riot_rs::embassy::network_stack; use riot_rs::rt::debug::println; use embassy_net::tcp::TcpSocket; @@ -87,63 +85,49 @@ async fn web_task( } } -struct WebServer { +// TODO: macro up this up +use riot_rs::embassy::{arch::OptionalPeripherals, Spawner}; +#[riot_rs::embassy::distributed_slice(riot_rs::embassy::EMBASSY_TASKS)] +#[linkme(crate = riot_rs::embassy::linkme)] +fn web_server_init(spawner: &Spawner, peripherals: &mut OptionalPeripherals) { #[cfg(feature = "button-readings")] - button_inputs: ButtonInputs, -} - -impl Application for WebServer { - fn initialize( - peripherals: &mut OptionalPeripherals, - ) -> Result<&dyn Application, ApplicationInitError> { - #[cfg(feature = "button-readings")] - let button_inputs = { - let buttons = pins::Buttons::take_from(peripherals)?; - - let buttons = Buttons { - button1: Input::new(buttons.btn1.degrade(), Pull::Up), - button2: Input::new(buttons.btn2.degrade(), Pull::Up), - button3: Input::new(buttons.btn3.degrade(), Pull::Up), - button4: Input::new(buttons.btn4.degrade(), Pull::Up), - }; - - ButtonInputs(make_static!(Mutex::new(buttons))) + let button_inputs = { + let buttons = pins::Buttons::take_from(peripherals).unwrap(); + + let buttons = Buttons { + button1: Input::new(buttons.btn1.degrade(), Pull::Up), + button2: Input::new(buttons.btn2.degrade(), Pull::Up), + button3: Input::new(buttons.btn3.degrade(), Pull::Up), + button4: Input::new(buttons.btn4.degrade(), Pull::Up), }; - Ok(make_static!(Self { - #[cfg(feature = "button-readings")] - button_inputs, - })) - } + ButtonInputs(make_static!(Mutex::new(buttons))) + }; - fn start(&self, spawner: embassy_executor::Spawner) { - fn make_app() -> picoserve::Router { - let router = picoserve::Router::new().route("/", get(routes::index)); - #[cfg(feature = "button-readings")] - let router = router.route("/buttons", get(routes::buttons)); - router - } + fn make_app() -> picoserve::Router { + let router = picoserve::Router::new().route("/", get(routes::index)); + #[cfg(feature = "button-readings")] + let router = router.route("/buttons", get(routes::buttons)); + router + } - let app = make_static!(make_app()); + let app = make_static!(make_app()); - let config = make_static!(picoserve::Config::new(picoserve::Timeouts { - start_read_request: Some(Duration::from_secs(5)), - read_request: Some(Duration::from_secs(1)), - write: Some(Duration::from_secs(1)), - })); + let config = make_static!(picoserve::Config::new(picoserve::Timeouts { + start_read_request: Some(Duration::from_secs(5)), + read_request: Some(Duration::from_secs(1)), + write: Some(Duration::from_secs(1)), + })); - for id in 0..WEB_TASK_POOL_SIZE { - let app_state = AppState { - #[cfg(feature = "button-readings")] - buttons: self.button_inputs, - }; - spawner.spawn(web_task(id, app, config, app_state)).unwrap(); - } + for id in 0..WEB_TASK_POOL_SIZE { + let app_state = AppState { + #[cfg(feature = "button-readings")] + buttons: button_inputs, + }; + spawner.spawn(web_task(id, app, config, app_state)).unwrap(); } } -riot_rs::embassy::riot_initialize!(WebServer); - #[no_mangle] fn riot_rs_network_config() -> embassy_net::Config { use embassy_net::Ipv4Address; diff --git a/examples/embassy-net-tcp/src/main.rs b/examples/embassy-net-tcp/src/main.rs index 14ff936c1..2c40d6d79 100644 --- a/examples/embassy-net-tcp/src/main.rs +++ b/examples/embassy-net-tcp/src/main.rs @@ -3,7 +3,7 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, network_stack, Application, ApplicationInitError}; +use riot_rs::embassy::network_stack; use riot_rs::rt::debug::println; @@ -56,22 +56,14 @@ async fn tcp_echo() { } } -struct TcpEcho {} - -impl Application for TcpEcho { - fn initialize( - _peripherals: &mut arch::OptionalPeripherals, - ) -> Result<&dyn Application, ApplicationInitError> { - Ok(&Self {}) - } - - fn start(&self, spawner: embassy_executor::Spawner) { - spawner.spawn(tcp_echo()).unwrap(); - } +// TODO: macro up this +use riot_rs::embassy::{arch::OptionalPeripherals, Spawner}; +#[riot_rs::embassy::distributed_slice(riot_rs::embassy::EMBASSY_TASKS)] +#[linkme(crate = riot_rs::embassy::linkme)] +fn __init_tcp_echo(spawner: &Spawner, _peripherals: &mut OptionalPeripherals) { + spawner.spawn(tcp_echo()).unwrap(); } -riot_rs::embassy::riot_initialize!(TcpEcho); - #[no_mangle] fn riot_rs_network_config() -> embassy_net::Config { use embassy_net::Ipv4Address; diff --git a/examples/embassy-net-udp/src/main.rs b/examples/embassy-net-udp/src/main.rs index cce37c8a1..782c802bc 100644 --- a/examples/embassy-net-udp/src/main.rs +++ b/examples/embassy-net-udp/src/main.rs @@ -3,7 +3,7 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::embassy::{arch, network_stack, Application, ApplicationInitError}; +use riot_rs::embassy::network_stack; use riot_rs::rt::debug::println; @@ -61,22 +61,14 @@ async fn udp_echo() { } } -struct UdpEcho {} - -impl Application for UdpEcho { - fn initialize( - _peripherals: &mut arch::OptionalPeripherals, - ) -> Result<&dyn Application, ApplicationInitError> { - Ok(&Self {}) - } - - fn start(&self, spawner: embassy_executor::Spawner) { - spawner.spawn(udp_echo()).unwrap(); - } +// TODO: macro up this +use riot_rs::embassy::{arch::OptionalPeripherals, Spawner}; +#[riot_rs::embassy::distributed_slice(riot_rs::embassy::EMBASSY_TASKS)] +#[linkme(crate = riot_rs::embassy::linkme)] +fn __init_udp_echo(spawner: &Spawner, _peripherals: &mut OptionalPeripherals) { + spawner.spawn(udp_echo()).unwrap(); } -riot_rs::embassy::riot_initialize!(UdpEcho); - #[no_mangle] fn riot_rs_network_config() -> embassy_net::Config { use embassy_net::Ipv4Address; diff --git a/examples/laze.yml b/examples/laze.yml index a7a53c748..dd66ac1cb 100644 --- a/examples/laze.yml +++ b/examples/laze.yml @@ -4,7 +4,6 @@ defaults: - riot-rs subdirs: - - application - benchmark - bottles - core-sizes From f13cbf9f3727b809d0a5355b5a587f01de928e33 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 22:40:47 +0100 Subject: [PATCH 10/13] riot-rs-embasssy: introduce `Delegate` --- src/riot-rs-embassy/src/delegate.rs | 83 +++++++++++++++++++++++++++++ src/riot-rs-embassy/src/lib.rs | 1 + 2 files changed, 84 insertions(+) create mode 100644 src/riot-rs-embassy/src/delegate.rs diff --git a/src/riot-rs-embassy/src/delegate.rs b/src/riot-rs-embassy/src/delegate.rs new file mode 100644 index 000000000..6675d05b6 --- /dev/null +++ b/src/riot-rs-embassy/src/delegate.rs @@ -0,0 +1,83 @@ +//! Delegate or lend an object to another task + +use embassy_executor::Spawner; +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; + +use crate::sendcell::SendCell; + +/// `Delegate` or lend an object to another task. +/// +/// This struct can be used to lend a `&mut T` to another task on the same executor. +/// The other task can then call a closure on it. +/// +/// This is supposed to be a `static`. +/// +/// Under the hood, `Delegate` leverages `SendCell` to ensure the delegated +/// object stays on the same executor. +/// +/// Example: +/// ```Rust +/// static SOME_VALUE: Delegate = Delegate::new(); +/// +/// // in some task +/// async fn foo() { +/// let mut my_val = 0u32; +/// SOME_VALUE.lend(&mut my_val); +/// assert_eq!(my_val, 1); +/// } +/// +/// // in some other task +/// async fn bar() { +/// SOME_VALUE.with(|val| *val = 1); +/// } +/// ``` +/// +/// TODO: this is a PoC implementation. +/// - takes 24b for each delegate (on arm), which seems too much. +/// - doesn't protect at all against calling `lend()` or `with()` multiple times +/// each, breaking safety assumptions. So while the API seems OK, the implementation +/// needs work. +pub struct Delegate { + send: Signal>, + reply: Signal, +} + +impl Delegate { + /// Create a new `Delegate`. + pub const fn new() -> Self { + Self { + send: Signal::new(), + reply: Signal::new(), + } + } + + /// Lend an object. + /// + /// This blocks until another task called `with()`. + pub async fn lend(&self, something: &mut T) { + let spawner = Spawner::for_current_executor().await; + self.send + .signal(SendCell::new(something as *mut T, &spawner)); + + self.reply.wait().await + } + + /// Call closure on a lended object. + /// + /// This blocks until another task called `lend(something)`. + pub async fn with(&self, func: impl FnOnce(&mut T) -> U) -> U { + let data = self.send.wait().await; + let spawner = Spawner::for_current_executor().await; + // SAFETY: + // - SendCell guarantees that data `lend()`ed stays on the same executor, + // which is single-threaded + // - `lend()` signals the raw pointer via `self.send`, but then waits for `self.reply` to be signaled. + // This function waits for the `self.send` signal, uses the dereferenced only inside the + // closure, then signals `self.reply` + // => the mutable reference is never used more than once + // TODO: it is actually possible to call `with()` twice, which breaks assumptions. + let result = func(unsafe { data.get(&spawner).unwrap().as_mut().unwrap() }); + self.reply.signal(()); + result + } +} diff --git a/src/riot-rs-embassy/src/lib.rs b/src/riot-rs-embassy/src/lib.rs index bf8126a5f..5e1fe50ee 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -32,6 +32,7 @@ pub use embassy_executor::Spawner; #[cfg(feature = "threading")] pub mod blocker; +pub mod delegate; pub mod sendcell; pub type Task = fn(&Spawner, &mut arch::OptionalPeripherals); From cb637fc57229faa92d54e5bc9b81988bccb324ac Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 22:42:40 +0100 Subject: [PATCH 11/13] riot-rs-embassy: move starting of tasks to front --- src/riot-rs-embassy/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/riot-rs-embassy/src/lib.rs b/src/riot-rs-embassy/src/lib.rs index 5e1fe50ee..1754e39e5 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -207,6 +207,12 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { while clock.events_hfclkstarted.read().bits() != 1 {} } + let spawner = Spawner::for_current_executor().await; + + for task in EMBASSY_TASKS { + task(&spawner, &mut peripherals); + } + #[cfg(feature = "usb")] let mut usb_builder = { let usb_config = usb_config(); @@ -247,8 +253,6 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { ) }; - let spawner = Spawner::for_current_executor().await; - #[cfg(feature = "usb")] { let usb = usb_builder.build(); @@ -309,10 +313,6 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { wifi::join(control).await; }; - for task in EMBASSY_TASKS { - task(&spawner, &mut peripherals); - } - // mark used let _ = peripherals; From 2dcbe82f710ac908f092930c91dff735bc830ad6 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Sat, 17 Feb 2024 22:44:09 +0100 Subject: [PATCH 12/13] riot-rs-embassy: provide usb builder hook --- src/riot-rs-embassy/src/lib.rs | 12 ++++++++++-- src/riot-rs-rt/linkme.x | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/riot-rs-embassy/src/lib.rs b/src/riot-rs-embassy/src/lib.rs index 1754e39e5..eaf4c18d8 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -27,7 +27,6 @@ pub mod arch; pub use linkme::{self, distributed_slice}; pub use static_cell::make_static; -use crate::define_peripherals::DefinePeripheralsError; pub use embassy_executor::Spawner; #[cfg(feature = "threading")] @@ -67,13 +66,17 @@ pub static EMBASSY_TASKS: [Task] = [..]; // // usb common start #[cfg(feature = "usb")] -use arch::usb::UsbDriver; +pub use arch::usb::UsbDriver; #[cfg(feature = "usb")] #[embassy_executor::task] async fn usb_task(mut device: embassy_usb::UsbDevice<'static, UsbDriver>) -> ! { device.run().await } + +#[cfg(feature = "usb")] +#[distributed_slice] +pub static USB_BUILDER_HOOKS: [&delegate::Delegate] = [..]; // usb common end // @@ -233,6 +236,11 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { builder }; + #[cfg(feature = "usb")] + for hook in USB_BUILDER_HOOKS { + hook.lend(&mut usb_builder).await; + } + // Our MAC addr. #[cfg(feature = "usb_ethernet")] let our_mac_addr = [0xCA, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; diff --git a/src/riot-rs-rt/linkme.x b/src/riot-rs-rt/linkme.x index f97a8fcef..a3b77f675 100644 --- a/src/riot-rs-rt/linkme.x +++ b/src/riot-rs-rt/linkme.x @@ -3,6 +3,7 @@ SECTIONS { linkm2_INIT_FUNCS : { *(linkm2_INIT_FUNCS) } > FLASH linkme_EMBASSY_TASKS : { *(linkme_EMBASSY_TASKS) } > FLASH linkm2_EMBASSY_TASKS : { *(linkm2_EMBASSY_TASKS) } > FLASH + linkm2_USB_BUILDER_HOOKS : { *(linkm2_USB_BUILDER_HOOKS) } > FLASH linkm2_THREAD_FNS : { *(linkm2_THREAD_FNS) } > FLASH } From 06970483a95aba63bc9e366c7dd27487e8479430 Mon Sep 17 00:00:00 2001 From: ROMemories Date: Fri, 12 Jan 2024 11:01:59 +0100 Subject: [PATCH 13/13] feat(example): add a USB HID keyboard example --- Cargo.lock | 88 ++++++++++++-- examples/embassy-usb-keyboard/Cargo.toml | 17 +++ examples/embassy-usb-keyboard/README.md | 16 +++ examples/embassy-usb-keyboard/laze.yml | 5 + examples/embassy-usb-keyboard/src/main.rs | 135 ++++++++++++++++++++++ examples/embassy-usb-keyboard/src/pins.rs | 12 ++ examples/laze.yml | 1 + 7 files changed, 262 insertions(+), 12 deletions(-) create mode 100644 examples/embassy-usb-keyboard/Cargo.toml create mode 100644 examples/embassy-usb-keyboard/README.md create mode 100644 examples/embassy-usb-keyboard/laze.yml create mode 100644 examples/embassy-usb-keyboard/src/main.rs create mode 100644 examples/embassy-usb-keyboard/src/pins.rs diff --git a/Cargo.lock b/Cargo.lock index 666aa43f6..27052d1e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,16 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "application" -version = "0.1.0" -dependencies = [ - "embassy-executor", - "embassy-time", - "riot-rs", - "riot-rs-boards", -] - [[package]] name = "arrayvec" version = "0.7.4" @@ -213,9 +203,9 @@ checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "c2rust-asm-casts" @@ -996,6 +986,8 @@ dependencies = [ "embassy-sync 0.5.0", "embassy-usb-driver", "heapless 0.8.0", + "ssmarshal", + "usbd-hid", ] [[package]] @@ -1004,6 +996,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" +[[package]] +name = "embassy-usb-keyboard" +version = "0.1.0" +dependencies = [ + "embassy-executor", + "embassy-nrf", + "embassy-sync 0.5.0", + "embassy-time", + "embassy-usb", + "riot-rs", + "riot-rs-boards", + "static_cell", + "usbd-hid", +] + [[package]] name = "embedded-dma" version = "0.1.2" @@ -1123,6 +1130,12 @@ dependencies = [ "log", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "equivalent" version = "1.0.1" @@ -2716,6 +2729,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2969,6 +2992,47 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "usb-device" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" + +[[package]] +name = "usbd-hid" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1" +dependencies = [ + "bitfield", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2" +dependencies = [ + "byteorder", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "vcell" version = "0.1.3" diff --git a/examples/embassy-usb-keyboard/Cargo.toml b/examples/embassy-usb-keyboard/Cargo.toml new file mode 100644 index 000000000..707517e69 --- /dev/null +++ b/examples/embassy-usb-keyboard/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "embassy-usb-keyboard" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +publish = false + +[dependencies] +riot-rs = { path = "../../src/riot-rs", features = ["time", "usb"] } +riot-rs-boards = { path = "../../src/riot-rs-boards" } +embassy-executor = { workspace = true, default-features = false } +embassy-nrf = { workspace = true, default-features = false } +embassy-sync = { workspace = true } +embassy-usb = { workspace = true, features = ["usbd-hid"] } +embassy-time = { workspace = true, default-features = false } +static_cell = { workspace = true } +usbd-hid = { version = "0.6.1"} diff --git a/examples/embassy-usb-keyboard/README.md b/examples/embassy-usb-keyboard/README.md new file mode 100644 index 000000000..cdb06b20a --- /dev/null +++ b/examples/embassy-usb-keyboard/README.md @@ -0,0 +1,16 @@ +# embassy-usb-keyboard + +## About + +This application is testing basic +[embassy](https://github.com/embassy-rs/embassy) _USB HID_ usage with RIOT-rs. + +## How to run + +In this folder, run + + laze build -b nrf52840dk run + +With the device USB cable connected, pressing ButtonĀ 1 should send the keycode +0x04 ('a') to the connected computer, and pressing ButtonĀ 2 should send keycode +0x05 ('b'). diff --git a/examples/embassy-usb-keyboard/laze.yml b/examples/embassy-usb-keyboard/laze.yml new file mode 100644 index 000000000..bece9350c --- /dev/null +++ b/examples/embassy-usb-keyboard/laze.yml @@ -0,0 +1,5 @@ +apps: + - name: embassy-usb-keyboard + context: nrf52840dk + selects: + - ?release diff --git a/examples/embassy-usb-keyboard/src/main.rs b/examples/embassy-usb-keyboard/src/main.rs new file mode 100644 index 000000000..4f5705784 --- /dev/null +++ b/examples/embassy-usb-keyboard/src/main.rs @@ -0,0 +1,135 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(used_with_arg)] + +use embassy_time::Duration; +use embassy_usb::class::hid::{self, HidReaderWriter}; +use riot_rs::embassy::{make_static, UsbDriver}; +use riot_rs::linkme::distributed_slice; +use riot_rs::rt::debug::println; + +use usbd_hid::descriptor::KeyboardReport; + +mod pins; + +// TODO: wrap in macro +use riot_rs::embassy::delegate::Delegate; +static USB_BUILDER_HOOK: Delegate = Delegate::new(); + +#[distributed_slice(riot_rs::embassy::USB_BUILDER_HOOKS)] +#[linkme(crate=riot_rs::embassy::linkme)] +static _USB_BUILDER_HOOK: &Delegate = &USB_BUILDER_HOOK; + +#[embassy_executor::task] +async fn usb_keyboard(button_peripherals: pins::Buttons) { + let mut buttons = Buttons::new(button_peripherals); + + let config = embassy_usb::class::hid::Config { + report_descriptor: ::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + + let hid_state = make_static!(hid::State::new()); + let hid_rw: HidReaderWriter<'static, UsbDriver, 1, 8> = USB_BUILDER_HOOK + .with(|usb_builder| hid::HidReaderWriter::new(usb_builder, hid_state, config)) + .await; + + let (_hid_reader, mut hid_writer) = hid_rw.split(); + + loop { + for (i, button) in buttons.get_mut().iter_mut().enumerate() { + if button.is_pressed() { + println!("Button #{} pressed", i + 1); + + let report = keyboard_report(KEYCODE_MAPPING[i]); + if let Err(e) = hid_writer.write_serialize(&report).await { + println!("Failed to send report: {:?}", e); + } + let report = keyboard_report(KEY_RELEASED); + if let Err(e) = hid_writer.write_serialize(&report).await { + println!("Failed to send report: {:?}", e); + } + } + } + + // Debounce events + embassy_time::Timer::after(Duration::from_millis(50)).await; + } +} + +// TODO: macro up this +use riot_rs::embassy::{arch::OptionalPeripherals, Spawner}; +#[riot_rs::embassy::distributed_slice(riot_rs::embassy::EMBASSY_TASKS)] +#[linkme(crate = riot_rs::embassy::linkme)] +fn __init_usb_keyboard(spawner: &Spawner, peripherals: &mut OptionalPeripherals) { + spawner + .spawn(usb_keyboard(pins::Buttons::take_from(peripherals).unwrap())) + .unwrap(); +} + +use crate::buttons::{Buttons, KEY_COUNT}; + +// Assuming a QWERTY US layout, see https://docs.qmk.fm/#/how_keyboards_work +// and https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf +const KC_A: u8 = 0x04; +const KC_C: u8 = 0x06; +const KC_G: u8 = 0x0a; +const KC_T: u8 = 0x17; + +const KEY_RELEASED: u8 = 0x00; + +fn keyboard_report(keycode: u8) -> KeyboardReport { + KeyboardReport { + keycodes: [keycode, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + } +} + +// Maps physical buttons to keycodes/characters +const KEYCODE_MAPPING: [u8; KEY_COUNT as usize] = [KC_A, KC_C, KC_G, KC_T]; + +mod buttons { + use embassy_nrf::gpio::{AnyPin, Input, Pin, Pull}; + + use crate::pins; + + pub const KEY_COUNT: u8 = 4; + + pub struct Button(Input<'static>); + + impl Button { + pub fn new(button: AnyPin) -> Self { + Self(Input::new(button, Pull::Up)) + } + + pub fn is_pressed(&mut self) -> bool { + self.0.is_low() + } + } + + pub struct Buttons([Button; KEY_COUNT as usize]); + + impl Buttons { + pub fn new(button_peripherals: pins::Buttons) -> Self { + Self([ + Button::new(button_peripherals.btn1.degrade()), + Button::new(button_peripherals.btn2.degrade()), + Button::new(button_peripherals.btn3.degrade()), + Button::new(button_peripherals.btn4.degrade()), + ]) + } + + pub fn get(&self) -> &[Button] { + &self.0 + } + + pub fn get_mut(&mut self) -> &mut [Button] { + &mut self.0 + } + } +} diff --git a/examples/embassy-usb-keyboard/src/pins.rs b/examples/embassy-usb-keyboard/src/pins.rs new file mode 100644 index 000000000..8d344f194 --- /dev/null +++ b/examples/embassy-usb-keyboard/src/pins.rs @@ -0,0 +1,12 @@ +use embassy_nrf::peripherals; +use riot_rs::define_peripherals; + +#[cfg(builder = "nrf52840dk")] +define_peripherals! { + Buttons { + btn1: P0_11, + btn2: P0_12, + btn3: P0_24, + btn4: P0_25, + } +} diff --git a/examples/laze.yml b/examples/laze.yml index dd66ac1cb..727400eb9 100644 --- a/examples/laze.yml +++ b/examples/laze.yml @@ -12,6 +12,7 @@ subdirs: - embassy-net-tcp - embassy-net-udp # - embassy-gpio + - embassy-usb-keyboard - hello-world - minimal - riot-app