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/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 f5e3bfcee..000000000 --- a/examples/application/src/main.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![feature(used_with_arg)] - -use riot_rs::embassy::{arch, Application, ApplicationInitError, Drivers, InitializationArgs}; - -use riot_rs::rt::debug::println; - -struct MyApplication { - state: u32, // some state -} - -impl Application for MyApplication { - fn initialize( - _peripherals: &mut arch::OptionalPeripherals, - _init_args: InitializationArgs, - ) -> Result<&dyn Application, ApplicationInitError> { - println!("MyApplication::initialize()"); - Ok(&Self { state: 0 }) - } - - fn start(&self, _spawner: embassy_executor::Spawner, _drivers: Drivers) { - 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 aa53bd713..4a969813e 100644 --- a/examples/embassy-http-server/src/main.rs +++ b/examples/embassy-http-server/src/main.rs @@ -8,10 +8,7 @@ mod routes; use riot_rs as _; -use riot_rs::embassy::{ - arch::OptionalPeripherals, Application, ApplicationInitError, Drivers, InitializationArgs, - NetworkStack, -}; +use riot_rs::embassy::network_stack; use riot_rs::rt::debug::println; use embassy_net::tcp::TcpSocket; @@ -54,11 +51,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]; @@ -87,68 +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, - _init_args: InitializationArgs, - ) -> 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 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 start(&self, spawner: embassy_executor::Spawner, drivers: Drivers) { - let stack = drivers.stack.get().unwrap(); + let app = make_static!(make_app()); - 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 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 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)), - })); - - 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, stack, 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 c768d9272..2c40d6d79 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, InitializationArgs}; +use riot_rs::embassy::network_stack; 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 = drivers.stack.get().unwrap(); + let stack = network_stack().await.unwrap(); let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; @@ -56,23 +56,14 @@ async fn tcp_echo(drivers: Drivers) { } } -struct TcpEcho {} - -impl Application for TcpEcho { - fn initialize( - _peripherals: &mut arch::OptionalPeripherals, - _init_args: InitializationArgs, - ) -> Result<&dyn Application, ApplicationInitError> { - Ok(&Self {}) - } - - fn start(&self, spawner: embassy_executor::Spawner, drivers: Drivers) { - spawner.spawn(tcp_echo(drivers)).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 3a0e6443a..782c802bc 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, InitializationArgs}; +use riot_rs::embassy::network_stack; 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 = drivers.stack.get().unwrap(); + let stack = network_stack().await.unwrap(); let mut rx_meta = [PacketMetadata::EMPTY; 16]; let mut rx_buffer = [0; 4096]; @@ -61,23 +61,14 @@ async fn udp_echo(drivers: Drivers) { } } -struct UdpEcho {} - -impl Application for UdpEcho { - fn initialize( - _peripherals: &mut arch::OptionalPeripherals, - _init_args: InitializationArgs, - ) -> Result<&dyn Application, ApplicationInitError> { - Ok(&Self {}) - } - - fn start(&self, spawner: embassy_executor::Spawner, drivers: Drivers) { - spawner.spawn(udp_echo(drivers)).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/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 a7a53c748..727400eb9 100644 --- a/examples/laze.yml +++ b/examples/laze.yml @@ -4,7 +4,6 @@ defaults: - riot-rs subdirs: - - application - benchmark - bottles - core-sizes @@ -13,6 +12,7 @@ subdirs: - embassy-net-tcp - embassy-net-udp # - embassy-gpio + - embassy-usb-keyboard - hello-world - minimal - riot-app 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 5815b6ac8..eaf4c18d8 100644 --- a/src/riot-rs-embassy/src/lib.rs +++ b/src/riot-rs-embassy/src/lib.rs @@ -23,42 +23,39 @@ pub mod define_peripherals; )] pub mod arch; -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; +pub use embassy_executor::Spawner; #[cfg(feature = "threading")] pub mod blocker; +pub mod delegate; +pub mod sendcell; -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(&Spawner, &mut arch::OptionalPeripherals); #[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; -#[derive(Copy, Clone)] -pub struct Drivers { - #[cfg(feature = "net")] - pub stack: &'static OnceCell<&'static NetworkStack>, +#[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())) } pub static EXECUTOR: arch::Executor = arch::Executor::new(); @@ -69,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 // @@ -199,11 +200,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 @@ -214,6 +210,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(); @@ -234,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]; @@ -254,8 +261,6 @@ async fn init_task(mut peripherals: arch::OptionalPeripherals) { ) }; - let spawner = Spawner::for_current_executor().await; - #[cfg(feature = "usb")] { let usb = usb_builder.build(); @@ -303,9 +308,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")] @@ -313,78 +321,8 @@ 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) { - Ok(initialized_application) => initialized_application.start(spawner, drivers), - Err(err) => panic!("Error while initializing an application: {err:?}"), - } - } - // mark used let _ = peripherals; 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, - init_args: InitializationArgs, - ) -> 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). - /// In addition, it is provided with the drivers initialized by the system. - fn start(&self, spawner: embassy_executor::Spawner, drivers: Drivers); -} - -/// 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, - init_args: $crate::InitializationArgs, - ) -> Result<&dyn $crate::Application, $crate::ApplicationInitError> { - <$prog_type as Application>::initialize(peripherals, init_args) - } - }; -} 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 + } + } +} 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 }