From 3c2dccd51c3b0ca2d2d1abfb3fc2d9b10b9ec6c9 Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Tue, 16 Apr 2024 15:11:05 +0000 Subject: [PATCH] Move the `esp-alloc` package into the repository (#1449) --- Cargo.toml | 1 + README.md | 1 - esp-alloc/Cargo.toml | 29 ++++++++ esp-alloc/README.md | 26 +++++++ esp-alloc/src/lib.rs | 160 ++++++++++++++++++++++++++++++++++++++++ esp-alloc/src/macros.rs | 41 ++++++++++ examples/Cargo.toml | 2 +- 7 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 esp-alloc/Cargo.toml create mode 100644 esp-alloc/README.md create mode 100644 esp-alloc/src/lib.rs create mode 100644 esp-alloc/src/macros.rs diff --git a/Cargo.toml b/Cargo.toml index 3cce6d82f..5ef6153f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = ["xtask"] exclude = [ + "esp-alloc", "esp-build", "esp-hal", "esp-hal-procmacros", diff --git a/README.md b/README.md index 36571f661..cb5591e40 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ There are a number of other crates within the [esp-rs organization] which can be | Crate | Description | | :--------------: | :----------------------------------------------------------------------------: | -| [esp-alloc] | A simple `no_std` heap allocator | | [esp-backtrace] | Backtrace support for bare-metal applications | | [esp-ieee802154] | Low-level IEEE802.15.4 driver for the ESP32-C6 and ESP32-H2 | | [esp-openthread] | A bare-metal Thread implementation using `esp-ieee802154` | diff --git a/esp-alloc/Cargo.toml b/esp-alloc/Cargo.toml new file mode 100644 index 000000000..8a1963863 --- /dev/null +++ b/esp-alloc/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "esp-alloc" +version = "0.3.0" +edition = "2021" +rust-version = "1.68" +description = "A heap allocator for Espressif devices" +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +keywords = [ + "allocator", + "esp32", + "riscv", + "xtensa", +] +categories = [ + "memory-management", + "no-std", +] + +[package.metadata.docs.rs] +default-target = "riscv32imc-unknown-none-elf" + +[dependencies] +critical-section = "1.1.1" +linked_list_allocator = { version = "0.10.5", default-features = false, features = ["const_mut_refs"] } + +[features] +nightly = [] diff --git a/esp-alloc/README.md b/esp-alloc/README.md new file mode 100644 index 000000000..851196573 --- /dev/null +++ b/esp-alloc/README.md @@ -0,0 +1,26 @@ +# esp-alloc + +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/esp-rs/esp-alloc/ci.yml?label=CI&logo=github&style=flat-square) +[![Crates.io](https://img.shields.io/crates/v/esp-alloc?color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-alloc) +[![docs.rs](https://img.shields.io/docsrs/esp-alloc?color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-alloc) +![MSRV](https://img.shields.io/badge/MSRV-1.68-blue?style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-alloc?style=flat-square) + +A simple `no_std` heap allocator for RISC-V and Xtensa processors from Espressif. Supports all currently available ESP32 devices. + +**NOTE:** using this as your global allocator requires using Rust 1.68 or greater, or the `nightly` release channel. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-alloc/src/lib.rs b/esp-alloc/src/lib.rs new file mode 100644 index 000000000..96193eec2 --- /dev/null +++ b/esp-alloc/src/lib.rs @@ -0,0 +1,160 @@ +//! A simple `no_std` heap allocator for RISC-V and Xtensa processors from +//! Espressif. Supports all currently available ESP32 devices. +//! +//! **NOTE:** using this as your global allocator requires using Rust 1.68 or +//! greater, or the `nightly` release channel. +//! +//! # Using this as your Global Allocator +//! To use EspHeap as your global allocator, you need at least Rust 1.68 or +//! nightly. +//! +//! ```rust +//! #[global_allocator] +//! static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); +//! +//! fn init_heap() { +//! const HEAP_SIZE: usize = 32 * 1024; +//! static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit(); +//! +//! unsafe { +//! ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE); +//! } +//! } +//! ``` +//! +//! # Using this with the nightly `allocator_api`-feature +//! Sometimes you want to have single allocations in PSRAM, instead of an esp's +//! DRAM. For that, it's convenient to use the nightly `allocator_api`-feature, +//! which allows you to specify an allocator for single allocations. +//! +//! **NOTE:** To use this, you have to enable the create's `nightly` feature +//! flag. +//! +//! Create and initialize an allocator to use in single allocations: +//! ```rust +//! static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); +//! +//! fn init_psram_heap() { +//! unsafe { +//! PSRAM_ALLOCATOR.init(psram::psram_vaddr_start() as *mut u8, psram::PSRAM_BYTES); +//! } +//! } +//! ``` +//! +//! And then use it in an allocation: +//! ```rust +//! let large_buffer: Vec = Vec::with_capacity_in(1048576, &PSRAM_ALLOCATOR); +//! ``` + +#![no_std] +#![cfg_attr(feature = "nightly", feature(allocator_api))] + +pub mod macros; + +#[cfg(feature = "nightly")] +use core::alloc::{AllocError, Allocator}; +use core::{ + alloc::{GlobalAlloc, Layout}, + cell::RefCell, + ptr::{self, NonNull}, +}; + +use critical_section::Mutex; +use linked_list_allocator::Heap; + +pub struct EspHeap { + heap: Mutex>, +} + +impl EspHeap { + /// Crate a new UNINITIALIZED heap allocator + /// + /// You must initialize this heap using the + /// [`init`](struct.EspHeap.html#method.init) method before using the + /// allocator. + pub const fn empty() -> EspHeap { + EspHeap { + heap: Mutex::new(RefCell::new(Heap::empty())), + } + } + + /// Initializes the heap + /// + /// This function must be called BEFORE you run any code that makes use of + /// the allocator. + /// + /// `heap_bottom` is a pointer to the location of the bottom of the heap. + /// + /// `size` is the size of the heap in bytes. + /// + /// Note that: + /// + /// - The heap grows "upwards", towards larger addresses. Thus `end_addr` + /// must be larger than `start_addr` + /// + /// - The size of the heap is `(end_addr as usize) - (start_addr as usize)`. + /// The allocator won't use the byte at `end_addr`. + /// + /// # Safety + /// + /// Obey these or Bad Stuff will happen. + /// + /// - This function must be called exactly ONCE. + /// - `size > 0` + pub unsafe fn init(&self, heap_bottom: *mut u8, size: usize) { + critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().init(heap_bottom, size)); + } + + /// Returns an estimate of the amount of bytes in use. + pub fn used(&self) -> usize { + critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().used()) + } + + /// Returns an estimate of the amount of bytes available. + pub fn free(&self) -> usize { + critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free()) + } +} + +unsafe impl GlobalAlloc for EspHeap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .allocate_first_fit(layout) + .ok() + .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) + }) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .deallocate(NonNull::new_unchecked(ptr), layout) + }); + } +} + +#[cfg(feature = "nightly")] +unsafe impl Allocator for EspHeap { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + critical_section::with(|cs| { + let raw_ptr = self + .heap + .borrow(cs) + .borrow_mut() + .allocate_first_fit(layout) + .map_err(|_| AllocError)? + .as_ptr(); + let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?; + Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) + }) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + self.dealloc(ptr.as_ptr(), layout); + } +} diff --git a/esp-alloc/src/macros.rs b/esp-alloc/src/macros.rs new file mode 100644 index 000000000..079a39e0b --- /dev/null +++ b/esp-alloc/src/macros.rs @@ -0,0 +1,41 @@ +//! Macros provided for convenience + +/// Create a heap allocator providing a heap of the given size in bytes +/// +/// You can only have ONE allocator at most +#[macro_export] +macro_rules! heap_allocator { + ($size:expr) => {{ + #[global_allocator] + static ALLOCATOR: $crate::EspHeap = $crate::EspHeap::empty(); + static mut HEAP: core::mem::MaybeUninit<[u8; $size]> = core::mem::MaybeUninit::uninit(); + + unsafe { + ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, $size); + } + }}; +} + +/// Create a heap allocator backed by PSRAM +/// +/// You can only have ONE allocator at most. You need a SoC which supports PSRAM +/// and activate the feature to enable it. You need to pass the PSRAM peripheral +/// and the psram module path. +/// +/// # Usage +/// ```no_run +/// esp_alloc::psram_allocator!(peripherals.PSRAM, hal::psram); +/// ``` +#[macro_export] +macro_rules! psram_allocator { + ($peripheral:expr,$psram_module:path) => {{ + #[global_allocator] + static ALLOCATOR: $crate::EspHeap = $crate::EspHeap::empty(); + + use $psram_module as _psram; + _psram::init_psram($peripheral); + unsafe { + ALLOCATOR.init(_psram::psram_vaddr_start() as *mut u8, _psram::PSRAM_BYTES); + } + }}; +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 0b2545acb..24268071f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -22,7 +22,7 @@ embedded-hal-async = "1.0.0" embedded-hal-bus = "0.1.0" embedded-io-async = "0.6.1" embedded-can = "0.4.1" -esp-alloc = "0.3.0" +esp-alloc = { version = "0.3.0", path = "../esp-alloc" } esp-backtrace = { version = "0.11.1", features = ["exception-handler", "panic-handler", "println"] } esp-hal = { version = "0.16.0", path = "../esp-hal", features = ["log"] } esp-hal-smartled = { version = "0.9.0", path = "../esp-hal-smartled", optional = true }