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 Feb 21, 2024
1 parent eb9c8ad commit 04f864d
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 150 deletions.
240 changes: 125 additions & 115 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions examples/embassy-http-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ edition.workspace = true
publish = false

[dependencies]
riot-rs = { path = "../../src/riot-rs", features = ["time", "override-network-config"] }
riot-rs = { path = "../../src/riot-rs", features = ["time", "override-network-config", "override-usb-config"] }
riot-rs-boards = { path = "../../src/riot-rs-boards" }
embassy-executor = { workspace = true, default-features = false }
embassy-sync = { workspace = true }
embassy-time = { workspace = true, default-features = false }
embassy-net = { workspace = true, features = ["tcp"] }
embedded-io-async = "0.6.0"
heapless = { workspace = true }
httparse = { version = "1.8.0", default-features = false }
Expand Down
23 changes: 20 additions & 3 deletions examples/embassy-http-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod routes;

use riot_rs as _;

use riot_rs::embassy::network;
use riot_rs::embassy::{embassy_net, embassy_usb, network};
use riot_rs::rt::debug::println;

use embassy_net::tcp::TcpSocket;
Expand Down Expand Up @@ -128,8 +128,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 @@ -138,3 +138,20 @@ fn riot_rs_network_config() -> embassy_net::Config {
gateway: Some(Ipv4Address::new(10, 42, 0, 1)),
})
}

#[riot_rs::config(usb)]
fn usb_config() -> embassy_usb::Config<'static> {
let mut config = 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 @@ -63,8 +63,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 @@ -68,8 +68,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
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")]
use usb::ethernet::NetworkDevice;

Expand Down
1 change: 1 addition & 0 deletions src/riot-rs-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
enum-iterator = "1.5.0"
proc-macro-crate = "3.1.0"
quote = "1.0.35"
syn = { version = "2.0.47", features = ["full"] }
Expand Down
127 changes: 127 additions & 0 deletions src/riot-rs-macros/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/// Register the function this attribute macro is applied on to provide the configuration for the
/// associated driver during initial system configuration.
///
/// 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 |
/// | --------- | ------------------------------ |
/// | `network` | `embassy_net::Config` |
/// | `usb` | `embassy_usb::Config<'static>` |
///
/// # 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 {
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)
}

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

impl ConfigAttributes {
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)]
enum ConfigKind {
Network,
Usb,
}

impl ConfigKind {
fn as_name(&self) -> &'static str {
match self {
Self::Network => "network",
Self::Usb => "usb",
}
}
}
5 changes: 5 additions & 0 deletions src/riot-rs-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#![deny(clippy::pedantic)]

mod utils;

use proc_macro::TokenStream;

include!("config.rs");
include!("thread.rs");
38 changes: 12 additions & 26 deletions src/riot-rs-macros/src/thread.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{meta::ParseNestedMeta, ItemFn, LitInt};

const RIOT_RS_CRATE_NAME: &str = "riot-rs";

// TODO: document default values (which may be platform-dependent)
// TODO: document valid values
/// Runs the function decorated with this attribute macro as a separate thread.
Expand Down Expand Up @@ -39,11 +33,13 @@ const RIOT_RS_CRATE_NAME: &str = "riot-rs";
/// this macro is used.
#[proc_macro_attribute]
pub fn thread(args: TokenStream, item: TokenStream) -> TokenStream {
use quote::{format_ident, quote};

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

let thread_function = syn::parse_macro_input!(item as ItemFn);
let thread_function = syn::parse_macro_input!(item as syn::ItemFn);

let no_mangle_attr = if attrs.no_mangle {
quote! {#[no_mangle]}
Expand All @@ -58,31 +54,21 @@ pub fn thread(args: TokenStream, item: TokenStream) -> TokenStream {
priority,
} = ThreadParameters::from(attrs);

let this_crate = proc_macro_crate::crate_name(RIOT_RS_CRATE_NAME)
.unwrap_or_else(|_| panic!("{RIOT_RS_CRATE_NAME} should be present in `Cargo.toml`"));
let this_crate = match this_crate {
proc_macro_crate::FoundCrate::Itself => {
panic!(
"{} cannot be used as a dependency of itself",
env!("CARGO_CRATE_NAME"),
);
}
proc_macro_crate::FoundCrate::Name(this_crate) => format_ident!("{}", this_crate),
};
let riot_rs_crate = utils::riot_rs_crate();

let expanded = quote! {
#no_mangle_attr
#[inline(always)]
#thread_function

#[#this_crate::linkme::distributed_slice(#this_crate::thread::THREAD_FNS)]
#[linkme(crate = #this_crate::linkme)]
#[#riot_rs_crate::linkme::distributed_slice(#riot_rs_crate::thread::THREAD_FNS)]
#[linkme(crate = #riot_rs_crate::linkme)]
fn #slice_fn_name_ident() {
fn trampoline(_arg: ()) {
#fn_name();
}
let stack = #this_crate::static_cell::make_static!([0u8; #stack_size as usize]);
#this_crate::thread::thread_create(trampoline, (), stack, #priority);
let stack = #riot_rs_crate::static_cell::make_static!([0u8; #stack_size as usize]);
#riot_rs_crate::thread::thread_create(trampoline, (), stack, #priority);
}
};

Expand Down Expand Up @@ -123,7 +109,7 @@ impl From<ThreadAttributes> for ThreadParameters {
}
}

fn parse_base10_or_panic<I>(lit_int: &LitInt, attr: &str) -> I
fn parse_base10_or_panic<I>(lit_int: &syn::LitInt, attr: &str) -> I
where
I: core::str::FromStr,
<I as core::str::FromStr>::Err: std::fmt::Display,
Expand All @@ -141,13 +127,13 @@ where

#[derive(Default)]
struct ThreadAttributes {
stack_size: Option<LitInt>,
priority: Option<LitInt>,
stack_size: Option<syn::LitInt>,
priority: Option<syn::LitInt>,
no_mangle: bool,
}

impl ThreadAttributes {
fn parse(&mut self, meta: &ParseNestedMeta) -> syn::Result<()> {
fn parse(&mut self, meta: &syn::meta::ParseNestedMeta) -> syn::Result<()> {
if meta.path.is_ident("stacksize") {
self.stack_size = Some(meta.value()?.parse()?);
Ok(())
Expand Down
18 changes: 18 additions & 0 deletions src/riot-rs-macros/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use quote::format_ident;

const RIOT_RS_CRATE_NAME: &str = "riot-rs";

pub fn riot_rs_crate() -> syn::Ident {
let riot_rs_crate = proc_macro_crate::crate_name(RIOT_RS_CRATE_NAME)
.unwrap_or_else(|_| panic!("{RIOT_RS_CRATE_NAME} should be present in `Cargo.toml`"));

match riot_rs_crate {
proc_macro_crate::FoundCrate::Itself => {
panic!(
"{} cannot be used as a dependency of itself",
env!("CARGO_CRATE_NAME"),
);
}
proc_macro_crate::FoundCrate::Name(riot_rs_crate) => format_ident!("{}", riot_rs_crate),
}
}
3 changes: 3 additions & 0 deletions src/riot-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ pub use riot_rs_buildinfo as buildinfo;
pub use riot_rs_embassy::{self as embassy, define_peripherals};
pub use riot_rs_rt as rt;

// Attribute macros
pub use riot_rs_macros::config;
#[cfg(feature = "threading")]
pub use riot_rs_macros::thread;

#[cfg(feature = "threading")]
pub use riot_rs_threads as thread;

Expand Down

0 comments on commit 04f864d

Please sign in to comment.