Skip to content

Commit

Permalink
feat(config): add a config attribute macro
Browse files Browse the repository at this point in the history
This attribute macro allows to register specific functions for
customizing the initial driver setup.
  • Loading branch information
ROMemories committed Mar 13, 2024
1 parent 1c675e0 commit 1417237
Show file tree
Hide file tree
Showing 22 changed files with 431 additions and 129 deletions.
259 changes: 147 additions & 112 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions examples/embassy-http-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ static_cell = { workspace = true }

embassy-nrf = { workspace = true, optional = true }

[target.'cfg(capability = "hw/usb-device-port")'.dependencies]
riot-rs = { path = "../../src/riot-rs", features = ["override-usb-config"] }

[features]
button-readings = ["dep:embassy-nrf"]
22 changes: 20 additions & 2 deletions examples/embassy-http-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ fn web_server_init(spawner: &Spawner, peripherals: &mut OptionalPeripherals) {
}
}

#[no_mangle]
fn riot_rs_network_config() -> embassy_net::Config {
#[riot_rs::config(network)]
fn network_config() -> embassy_net::Config {
use embassy_net::Ipv4Address;

embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
Expand All @@ -135,3 +135,21 @@ fn riot_rs_network_config() -> embassy_net::Config {
gateway: Some(Ipv4Address::new(10, 42, 0, 1)),
})
}

#[cfg(capability = "hw/usb-device-port")]
#[riot_rs::config(usb)]
fn usb_config() -> riot_rs::embassy::embassy_usb::Config<'static> {
let mut config = riot_rs::embassy::embassy_usb::Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("HTTP-over-USB-Ethernet example");
config.serial_number = Some("12345678");
config.max_power = 100;
config.max_packet_size_0 = 64;

// Required for Windows support.
config.composite_with_iads = true;
config.device_class = 0xEF;
config.device_sub_class = 0x02;
config.device_protocol = 0x01;
config
}
4 changes: 2 additions & 2 deletions examples/embassy-net-tcp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ fn __init_tcp_echo(spawner: &Spawner, _peripherals: &mut OptionalPeripherals) {
spawner.spawn(tcp_echo()).unwrap();
}

#[no_mangle]
fn riot_rs_network_config() -> embassy_net::Config {
#[riot_rs::config(network)]
fn network_config() -> embassy_net::Config {
use embassy_net::Ipv4Address;

embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
Expand Down
4 changes: 2 additions & 2 deletions examples/embassy-net-udp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ fn __init_udp_echo(spawner: &Spawner, _peripherals: &mut OptionalPeripherals) {
spawner.spawn(udp_echo()).unwrap();
}

#[no_mangle]
fn riot_rs_network_config() -> embassy_net::Config {
#[riot_rs::config(network)]
fn network_config() -> embassy_net::Config {
use embassy_net::Ipv4Address;

embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
Expand Down
2 changes: 1 addition & 1 deletion examples/embassy-usb-keyboard/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ embassy-nrf = { workspace = true, default-features = false }
embassy-sync = { workspace = true }
embassy-time = { workspace = true, default-features = false }
embassy-usb = { workspace = true, features = ["usbd-hid"] }
riot-rs = { path = "../../src/riot-rs", features = ["time", "usb"] }
riot-rs = { path = "../../src/riot-rs", features = ["time", "usb", "override-usb-config"] }
riot-rs-boards = { path = "../../src/riot-rs-boards" }
static_cell = { workspace = true }
usbd-hid = { version = "0.6.1" }
17 changes: 17 additions & 0 deletions examples/embassy-usb-keyboard/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,20 @@ mod buttons {
}
}
}

#[riot_rs::config(usb)]
fn usb_config() -> riot_rs::embassy::embassy_usb::Config<'static> {
let mut config = riot_rs::embassy::embassy_usb::Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("HID keyboard example");
config.serial_number = Some("12345678");
config.max_power = 100;
config.max_packet_size_0 = 64;

// Required for Windows support.
config.composite_with_iads = true;
config.device_class = 0xEF;
config.device_sub_class = 0x02;
config.device_protocol = 0x01;
config
}
8 changes: 6 additions & 2 deletions laze-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -450,17 +450,21 @@ modules:
provides_unique:
- network_device
selects:
- usb_device_port
- hw/usb-device-port
env:
global:
FEATURES:
- riot-rs/usb-ethernet

- name: usb_device_port
- name: hw/usb-device-port
context:
- nrf52840dk
- rpi-pico
- rpi-pico-w
env:
global:
RUSTFLAGS:
- --cfg capability=\"hw/usb-device-port\"

- name: wifi-esp
context:
Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-embassy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ pub use static_cell::make_static;

pub use embassy_executor::Spawner;

// Crates used in driver configuration functions
#[cfg(feature = "net")]
pub use embassy_net;
#[cfg(feature = "usb")]
pub use embassy_usb;

#[cfg(feature = "usb-ethernet")]
use usb::ethernet::NetworkDevice;

Expand Down
2 changes: 2 additions & 0 deletions src/riot-rs-embassy/src/network.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! To provide a custom network configuration, use the `riot_rs::config` attribute macro.

use core::cell::OnceCell;

use embassy_executor::Spawner;
Expand Down
9 changes: 1 addition & 8 deletions src/riot-rs-embassy/src/usb.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
//! To provide a custom USB configuration, enable the feature
//! `riot_rs_embassy/override-usb-config`, then add this to your code:
//! ```rust
//! #[no_mangle]
//! pub fn riot_rs_usb_config() -> embassy_usb::Config<'static> {
//! /// create config here
//! }
//! ```
//! To provide a custom USB configuration, use the `riot_rs::config` attribute macro.

pub use crate::arch::usb::UsbDriver;

Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ repository.workspace = true
workspace = true

[dependencies]
enum-iterator = "1.5.0"
proc-macro-crate = "3.1.0"
quote = "1.0.35"
syn = { version = "2.0.47", features = ["full"] }

[dev-dependencies]
heapless = { workspace = true }
riot-rs = { workspace = true, features = ["no-boards", "usb-ethernet", "override-network-config"] }
trybuild = "1.0.89"

[lib]
proc-macro = true
135 changes: 135 additions & 0 deletions src/riot-rs-macros/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/// Registers the function this attribute macro is applied on to provide the configuration for the
/// associated driver during initial system configuration.
///
/// **Important**: for this configuration to be taken into account, a specific Cargo feature may
/// need to be enabled on the `riot-rs` dependency, for each configuration type (see table below).
///
/// The name of the function does not matter as it will be renamed by the macro.
///
/// # Parameters
///
/// - The name of the driver the function provides configuration for.
///
/// | Driver | Expected return type | Cargo feature to enable |
/// | --------- | ------------------------------ | ------------------------- |
/// | `network` | `embassy_net::Config` | `override-network-config` |
/// | `usb` | `embassy_usb::Config<'static>` | `override-usb-config` |
///
/// # Note
///
/// The `riot_rs` crate provides re-exports for the relevant Embassy crates.
///
/// # Examples
///
/// The following function provides configuration for the network stack:
///
/// ```ignore
/// use riot_rs::embassy_net;
///
/// #[riot_rs::config(network)]
/// fn network_config() -> embassy_net::Config {
/// use embassy_net::Ipv4Address;
///
/// embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
/// address: embassy_net::Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24),
/// dns_servers: heapless::Vec::new(),
/// gateway: Some(Ipv4Address::new(10, 42, 0, 1)),
/// })
/// }
/// ```
#[allow(clippy::missing_panics_doc)]
#[proc_macro_attribute]
pub fn config(args: TokenStream, item: TokenStream) -> TokenStream {
#[allow(clippy::wildcard_imports)]
use config_macro::*;

use quote::{format_ident, quote};

let mut attrs = ConfigAttributes::default();
let thread_parser = syn::meta::parser(|meta| attrs.parse(&meta));
syn::parse_macro_input!(args with thread_parser);

let config_function = syn::parse_macro_input!(item as syn::ItemFn);
let config_function_name = &config_function.sig.ident;

let riot_rs_crate = utils::riot_rs_crate();

let (config_fn_name, return_type) = match attrs.kind {
Some(ConfigKind::Network) => (
format_ident!("riot_rs_network_config"),
quote! {#riot_rs_crate::embassy::embassy_net::Config},
),
Some(ConfigKind::Usb) => (
format_ident!("riot_rs_usb_config"),
quote! {#riot_rs_crate::embassy::embassy_usb::Config<'static>},
),
None => {
panic!("a configuration kind must be specified");
}
};

// Place the provided function into another function whose type signature we enforce.
// This is important as that function will be called unsafely via FFI.
let expanded = quote! {
#[no_mangle]
fn #config_fn_name() -> #return_type {
#[inline(always)]
#config_function

#config_function_name()
}
};

TokenStream::from(expanded)
}

mod config_macro {
#[derive(Default)]
pub struct ConfigAttributes {
pub kind: Option<ConfigKind>,
}

impl ConfigAttributes {
pub fn parse(&mut self, meta: &syn::meta::ParseNestedMeta) -> syn::Result<()> {
use enum_iterator::all;

for (config_name, kind) in all::<ConfigKind>().map(|c| (c.as_name(), c)) {
if meta.path.is_ident(config_name) {
self.check_only_one_kind(config_name);
self.kind = Some(kind);
return Ok(());
}
}

let supported_params = all::<ConfigKind>()
.map(|c| format!("`{}`", c.as_name()))
.collect::<Vec<_>>()
.join(", ");
Err(meta.error(format!(
"unsupported parameter ({supported_params} are supported)",
)))
}

fn check_only_one_kind(&self, param: &str) {
assert!(
self.kind.is_none(),
"a separate function is required for `{param}` configuration",
);
}
}

#[derive(Debug, enum_iterator::Sequence)]
pub enum ConfigKind {
Network,
Usb,
}

impl ConfigKind {
pub fn as_name(&self) -> &'static str {
match self {
Self::Network => "network",
Self::Usb => "usb",
}
}
}
}
1 change: 1 addition & 0 deletions src/riot-rs-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ mod utils;

use proc_macro::TokenStream;

include!("config.rs");
include!("thread.rs");
5 changes: 5 additions & 0 deletions src/riot-rs-macros/tests/macro_errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}
19 changes: 19 additions & 0 deletions src/riot-rs-macros/tests/ui/config_conflicting_config_kind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// #![no_std]
#![no_main]

// This will not get used because the attribute macro is expected to fail
#[allow(unused_imports)]
use riot_rs::embassy::embassy_net;

// FAIL: network and usb cannot be used at the same time
#[riot_rs::config(network, usb)]
fn network_config() -> embassy_net::Config {
use embassy_net::Ipv4Address;

embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
address: embassy_net::Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24),
dns_servers: heapless::Vec::new(),
gateway: Some(Ipv4Address::new(10, 42, 0, 1)),
})
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
error: custom attribute panicked
--> tests/ui/config_conflicting_config_kind.rs:9:1
|
9 | #[riot_rs::config(network, usb)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: a separate function is required for `usb` configuration
18 changes: 18 additions & 0 deletions src/riot-rs-macros/tests/ui/config_misspelled_config_kind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// #![no_std]
#![no_main]

// This will not get used because the attribute macro is expected to fail
#[allow(unused_imports)]
use riot_rs::embassy::embassy_net;

// FAIL: misspelled config kind
#[riot_rs::config(networkk)]
fn network_config() -> embassy_net::Config {
use embassy_net::Ipv4Address;

embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
address: embassy_net::Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24),
dns_servers: heapless::Vec::new(),
gateway: Some(Ipv4Address::new(10, 42, 0, 1)),
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: unsupported parameter (`network`, `usb` are supported)
--> tests/ui/config_misspelled_config_kind.rs:9:19
|
9 | #[riot_rs::config(networkk)]
| ^^^^^^^^
18 changes: 18 additions & 0 deletions src/riot-rs-macros/tests/ui/config_no_parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// #![no_std]
#![no_main]

// This will not get used because the attribute macro is expected to fail
#[allow(unused_imports)]
use riot_rs::embassy::embassy_net;

// FAIL: this attribute macro requires parameters
#[riot_rs::config]
fn network_config() -> embassy_net::Config {
use embassy_net::Ipv4Address;

embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
address: embassy_net::Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24),
dns_servers: heapless::Vec::new(),
gateway: Some(Ipv4Address::new(10, 42, 0, 1)),
})
}
7 changes: 7 additions & 0 deletions src/riot-rs-macros/tests/ui/config_no_parameters.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
error: custom attribute panicked
--> tests/ui/config_no_parameters.rs:9:1
|
9 | #[riot_rs::config]
| ^^^^^^^^^^^^^^^^^^
|
= help: message: a configuration kind must be specified
Loading

0 comments on commit 1417237

Please sign in to comment.