Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/.vscode
/target
/Cargo.lock
*.rlib
130 changes: 130 additions & 0 deletions src/bump_alloc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/// A simple bump allocator for fixed-size memory chunks
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is anything in this module which is core::future::Future specific? If so, remove all mentions of "futures" from it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all future-specific references. The bump allocator is now generic and works with any type T, not just futures. Updated documentation and method names accordingly.

Commit: 6aabfa2

///
/// This allocator is designed for async futures that have a known maximum lifetime.
/// All allocations are freed when the allocator is dropped.
use core::alloc::Layout;
use core::cell::Cell;
use core::mem::MaybeUninit;
use core::pin::Pin;
use core::ptr::NonNull;

/// A bump allocator that uses a provided memory chunk
pub struct BumpAllocator<'a> {
memory: &'a mut [MaybeUninit<u8>],
offset: Cell<usize>,
}

impl<'a> BumpAllocator<'a> {
/// Create a new bump allocator with the provided memory chunk
pub fn new(memory: &'a mut [MaybeUninit<u8>]) -> Self {
Self {
memory,
offset: Cell::new(0),
}
}

/// Allocate memory for a future and pin it
pub fn alloc_pin<F>(&mut self, future: F) -> Result<Pin<BumpBox<'_, F>>, AllocError>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the F type in this method constrained to core::future::Future? Wouldn't that code work for any type F (in that case remove the constraint and rename F to T).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct! Removed the Future constraint and changed F to T. The alloc_pin method now works for any type T: Sized.

Commit: 6aabfa2

where
F: core::future::Future,
{
let layout = Layout::new::<F>();
let ptr = self.alloc_raw(layout)?;

// Safety: We just allocated the memory and it's properly aligned
unsafe {
let ptr = ptr.as_ptr() as *mut F;
ptr.write(future);
Ok(Pin::new_unchecked(BumpBox {
ptr: NonNull::new_unchecked(ptr),
_allocator: core::marker::PhantomData,
}))
}
}

fn alloc_raw(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocError> {
let size = layout.size();
let align = layout.align();

let current_offset = self.offset.get();

// Align the offset
let aligned_offset = (current_offset + align - 1) & !(align - 1);
let new_offset = aligned_offset + size;

if new_offset > self.memory.len() {
return Err(AllocError);
}

self.offset.set(new_offset);

// Safety: We checked bounds and alignment
let ptr = unsafe {
self.memory.as_mut_ptr().add(aligned_offset) as *mut u8
};

Ok(unsafe { NonNull::new_unchecked(ptr) })
}

/// Get the current memory usage
pub fn used(&self) -> usize {
self.offset.get()
}

/// Get the total capacity
pub fn capacity(&self) -> usize {
self.memory.len()
}
}

/// A box-like container that uses bump allocation
pub struct BumpBox<'a, T> {
ptr: NonNull<T>,
_allocator: core::marker::PhantomData<&'a ()>,
}

impl<T> core::ops::Deref for BumpBox<'_, T> {
type Target = T;

fn deref(&self) -> &Self::Target {
unsafe { self.ptr.as_ref() }
}
}

impl<T> core::ops::DerefMut for BumpBox<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.ptr.as_mut() }
}
}

impl<T: core::future::Future> core::future::Future for BumpBox<'_, T> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need this indeed or we might not. Can you explain why you need to implement the Future trait on the BumpBox?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Future trait implementation on BumpBox is needed because the allocated objects are futures that need to be polled by select3() and similar combinators. Without this impl, the async runtime couldn't poll the bump-allocated futures.

When we do allocator.alloc_pin(some_future), the result needs to be usable wherever a Future is expected.

type Output = T::Output;

fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll<Self::Output> {
// Safety: We maintain the pin invariant
let future = unsafe { self.map_unchecked_mut(|s| &mut **s) };
future.poll(cx)
}
}

impl<T> Drop for BumpBox<'_, T> {
fn drop(&mut self) {
// Safety: The pointer is valid and we own the data
unsafe {
self.ptr.as_ptr().drop_in_place();
}
}
}

/// Error type for allocation failures
#[derive(Debug)]
pub struct AllocError;

impl core::fmt::Display for AllocError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Bump allocator out of memory")
}
}

#[cfg(feature = "std")]
impl std::error::Error for AllocError {}
116 changes: 113 additions & 3 deletions src/eth.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use core::pin::pin;
use core::mem::MaybeUninit;
extern crate alloc;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all references to the alloc module. These are from your previous commit, which is now obsoleted by the bump allocator.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all alloc module references and Box::pin usage. The ethernet implementation now uses the bump allocator approach instead of heap allocations.

Commit: 6aabfa2

use alloc::boxed::Box;

use embassy_futures::select::{select, select3};

use crate::bump_alloc::BumpAllocator;

use rs_matter::dm::clusters::gen_comm::CommPolicy;
use rs_matter::dm::clusters::gen_diag::{GenDiag, NetifDiag};
use rs_matter::dm::clusters::net_comm::NetworkType;
Expand Down Expand Up @@ -247,6 +252,49 @@ where
select(&mut net_task, &mut persist_task).coalesce().await
}

/// Run the Matter stack for an Ethernet network with bump allocation.
/// Uses the provided memory buffer for allocations instead of heap.
///
/// Parameters:
/// - `ethernet` - a user-provided `Ethernet` implementation
/// - `persist` - a user-provided `Persist` implementation
/// - `handler` - a user-provided DM handler implementation
/// - `user` - a user-provided future that will be polled only when the netif interface is up
/// - `memory` - a memory buffer for internal allocations (recommended size: 16KB+)
pub async fn run_with_memory<N, S, H, X>(
&self,
ethernet: N,
store: &SharedKvBlobStore<'_, S>,
handler: H,
user: X,
memory: &mut [MaybeUninit<u8>],
) -> Result<(), Error>
where
N: Ethernet,
S: KvBlobStore,
H: AsyncHandler + AsyncMetadata,
X: UserTask,
{
info!("Matter Stack memory: {}B", core::mem::size_of_val(self));

let persist = self.create_persist(store);

persist.load().await?;

self.matter().reset_transport()?;

if !self.is_commissioned().await? {
self.matter()
.enable_basic_commissioning(DiscoveryCapabilities::IP, 0)
.await?; // TODO
}

let mut net_task = pin!(self.run_ethernet_with_memory(ethernet, handler, user, memory));
let mut persist_task = pin!(self.run_psm(&persist));

select(&mut net_task, &mut persist_task).coalesce().await
}

async fn run_ethernet<N, H, X>(&self, mut ethernet: N, handler: H, user: X) -> Result<(), Error>
where
N: Ethernet,
Expand All @@ -255,6 +303,21 @@ where
{
Ethernet::run(&mut ethernet, MatterStackEthernetTask(self, handler, user)).await
}

async fn run_ethernet_with_memory<N, H, X>(
&self,
mut ethernet: N,
handler: H,
user: X,
memory: &mut [MaybeUninit<u8>]
) -> Result<(), Error>
where
N: Ethernet,
H: AsyncHandler + AsyncMetadata,
X: UserTask,
{
Ethernet::run(&mut ethernet, MatterStackEthernetTaskWithMemory(self, handler, user, memory)).await
}
}

struct MatterStackEthernetTask<'a, E, H, X>(&'a MatterStack<'a, Eth<E>>, H, X)
Expand All @@ -263,6 +326,12 @@ where
H: AsyncMetadata + AsyncHandler,
X: UserTask;

struct MatterStackEthernetTaskWithMemory<'a, E, H, X>(&'a MatterStack<'a, Eth<E>>, H, X, &'a mut [MaybeUninit<u8>])
where
E: Embedding + 'static,
H: AsyncMetadata + AsyncHandler,
X: UserTask;

impl<E, H, X> EthernetTask for MatterStackEthernetTask<'_, E, H, X>
where
E: Embedding + 'static,
Expand All @@ -277,7 +346,8 @@ where
{
info!("Ethernet driver started");

let mut net_task = pin!(self.0.run_oper_net(
// Box the largest futures to reduce stack frame size
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all Box::pin calls which are from your previous commit. Restore the previous on-stack allocation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all Box::pin calls and restored original on-stack allocation for the default implementation. Only the new run_with_memory methods use bump allocation.

Commit: 6aabfa2

let net_task = Box::pin(self.0.run_oper_net(
&net_stack,
&netif,
&mut mdns,
Expand All @@ -286,11 +356,51 @@ where
));

let handler = self.0.root_handler(&(), &true, &netif, &self.1);
let mut handler_task = pin!(self.0.run_handler((&self.1, handler)));
let handler_task = Box::pin(self.0.run_handler((&self.1, handler)));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto: Remove all Box::pin calls which are from your previous commit. Restore the previous on-stack allocation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all Box::pin calls and restored original on-stack allocation for the default implementation. Only the new run_with_memory methods use bump allocation.

Commit: 6aabfa2


let mut user_task = pin!(self.2.run(&net_stack, &netif));

select3(&mut net_task, &mut handler_task, &mut user_task)
select3(net_task, handler_task, &mut user_task)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto: Remove all Box::pin calls which are from your previous commit. Restore the previous on-stack allocation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all Box::pin calls and restored original on-stack allocation for the default implementation. Only the new run_with_memory methods use bump allocation.

Commit: 6aabfa2

.coalesce()
.await
}
}

impl<E, H, X> EthernetTask for MatterStackEthernetTaskWithMemory<'_, E, H, X>
where
E: Embedding + 'static,
H: AsyncMetadata + AsyncHandler,
X: UserTask,
{
async fn run<S, C, M>(&mut self, net_stack: S, netif: C, mut mdns: M) -> Result<(), Error>
where
S: NetStack,
C: NetifDiag + NetChangeNotif,
M: Mdns,
{
info!("Ethernet driver started with bump allocator");

// Create bump allocator from provided memory
let mut allocator = BumpAllocator::new(self.3);

// Use bump allocator instead of Box::pin for largest futures
let net_task = allocator.alloc_pin(self.0.run_oper_net(
&net_stack,
&netif,
&mut mdns,
core::future::pending(),
Option::<(NoNetwork, NoNetwork)>::None,
)).map_err(|_| rs_matter::error::Error::new(rs_matter::error::ErrorCode::NoMemory, false))?;

let handler = self.0.root_handler(&(), &true, &netif, &self.1);
let handler_task = allocator.alloc_pin(self.0.run_handler((&self.1, handler)))
.map_err(|_| rs_matter::error::Error::new(rs_matter::error::ErrorCode::NoMemory, false))?;

let mut user_task = pin!(self.2.run(&net_stack, &netif));

info!("Bump allocator usage: {}/{} bytes", allocator.used(), allocator.capacity());

select3(net_task, handler_task, &mut user_task)
.coalesce()
.await
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#![warn(clippy::large_stack_frames)]
#![warn(clippy::large_types_passed_by_value)]

pub mod bump_alloc;

use core::future::Future;
use core::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6};
use core::pin::pin;
Expand Down
26 changes: 15 additions & 11 deletions src/wireless/thread.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use core::pin::pin;
extern crate alloc;
use alloc::boxed::Box;

use embassy_futures::select::{select, select3, select4};
use embassy_sync::blocking_mutex::raw::RawMutex;
Expand Down Expand Up @@ -415,12 +417,13 @@ where
NoopWirelessNetCtl::new(NetworkType::Thread),
);

let mut btp_task = pin!(self.0.run_btp(peripheral));
// Box the largest futures to reduce stack frame size
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto: Remove all Box::pin calls which are from your previous commit. Restore the previous on-stack allocation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on Thread stack implementation now. Will remove all Box::pin calls and add bump allocator support with the same pattern as Ethernet and WiFi stacks.

Next commit will include Thread support.

let btp_task = Box::pin(self.0.run_btp(peripheral));

let handler = self.0.root_handler(&(), &(), &net_ctl, &false, &self.1);
let mut handler_task = pin!(self.0.run_handler((&self.1, handler)));
let handler_task = Box::pin(self.0.run_handler((&self.1, handler)));

select(&mut btp_task, &mut handler_task).coalesce().await
select(btp_task, handler_task).coalesce().await
}
}

Expand Down Expand Up @@ -452,7 +455,8 @@ where

let stack = &mut self.0;

let mut net_task = pin!(stack.run_oper_net(
// Box the largest futures to reduce stack frame size
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all Box::pin calls which are from your previous commit. Restore the previous on-stack allocation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on Thread stack implementation now. Will remove all Box::pin calls and add bump allocator support with the same pattern as Ethernet and WiFi stacks.

Next commit will include Thread support.

let net_task = Box::pin(stack.run_oper_net(
&net_stack,
&netif,
&mut mdns,
Expand All @@ -466,14 +470,14 @@ where
let handler = self
.0
.root_handler(&(), &netif, &net_ctl_s, &false, &self.1);
let mut handler_task = pin!(self.0.run_handler((&self.1, handler)));
let handler_task = Box::pin(self.0.run_handler((&self.1, handler)));

let mut user_task = pin!(self.2.run(&net_stack, &netif));

select4(
&mut net_task,
net_task,
&mut mgr_task,
&mut handler_task,
handler_task,
&mut user_task,
)
.coalesce()
Expand Down Expand Up @@ -507,17 +511,17 @@ where

let stack = &mut self.0;

let mut net_task =
pin!(stack.run_net_coex(&net_stack, &netif, &net_ctl, &mut mdns, &mut gatt));
// Box the largest futures to reduce stack frame size
let net_task = Box::pin(stack.run_net_coex(&net_stack, &netif, &net_ctl, &mut mdns, &mut gatt));

let net_ctl_s = NetCtlWithStatusImpl::new(&self.0.network.net_state, &net_ctl);

let handler = self.0.root_handler(&(), &netif, &net_ctl_s, &true, &self.1);
let mut handler_task = pin!(self.0.run_handler((&self.1, handler)));
let handler_task = Box::pin(self.0.run_handler((&self.1, handler)));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto: Remove all Box::pin calls which are from your previous commit. Restore the previous on-stack allocation.

In fact, why is the thread stack NOT re-worked to use the bump allocator instead of the heap?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on Thread stack implementation now. Will add bump allocator support with the same pattern as Ethernet and WiFi stacks.

Next commit will include Thread support.


let mut user_task = pin!(self.2.run(&net_stack, &netif));

select3(&mut net_task, &mut handler_task, &mut user_task)
select3(net_task, handler_task, &mut user_task)
.coalesce()
.await
}
Expand Down
Loading
Loading