1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Global heap ownership + external PSRAM integration.
//!
//! The BSP owns the global heap so a binary never spells out
//! `esp_alloc::heap_allocator!` or the per-board region sizes itself:
//! [`init_heap`] declares the esp-alloc DRAM regions for a [`HeapProfile`] and,
//! optionally, registers external PSRAM. esp-alloc's global heap holds at most
//! three regions, so each profile registers the reclaimed-ROM region, the
//! plain-DRAM region, and (when PSRAM is supplied) the external region — never a
//! fourth (a 4th `add_region` panics silently). The sizes are the HIL-proven
//! per-board values previously copied into every binary.
//!
//! ```ignore
//! use m5stack_core::mem::{self, HeapProfile};
//!
//! // After board init, before the first allocation:
//! mem::init_heap(HeapProfile::Default, Some(board.psram)); // DRAM + PSRAM
//! mem::init_heap(HeapProfile::Lvgl, None); // DRAM only
//! ```
//!
//! The PSRAM-specific surface below ([`init_psram_heap`], [`psram_split`], the
//! checked [`psram_box`] / [`psram_vec`], [`PsramSafe`]) needs the `psram`
//! feature; the heap regions and [`dma_buffer`] need only `heap`.
//!
//! Both boards carry SPI PSRAM (Fire27: ~4 MB, CoreS3: ~8 MB). `esp-alloc`
//! exposes a single global heap that can be backed by several regions;
//! [`init_psram_heap`] maps the external PSRAM and registers it as one such
//! region. After that, an application can allocate from it in two ways:
//!
//! 1. **Implicitly** — once registered, the global allocator may satisfy any
//! `alloc::vec!` / `Box` / `String` from PSRAM (internal DRAM is consumed
//! first, then it spills to PSRAM).
//! 2. **Explicitly** — pick the region per allocation. Prefer the *checked*
//! helpers [`psram_box`] / [`psram_vec`], which reject atomic-bearing types
//! at compile time (see [`PsramSafe`]):
//!
//! For a *private* region handed to a foreign allocator (not the global heap at
//! all) — e.g. LVGL's TLSF — use [`psram_split`] instead, which carves a private
//! slice off the base and registers only the remainder globally.
//!
//! ```ignore
//! use m5stack_core::mem;
//!
//! let psram_free = mem::init_psram_heap(peripherals.PSRAM);
//!
//! let mut big = mem::psram_vec::<u8>(512 * 1024); // in PSRAM, atomics rejected
//! let scratch = mem::psram_box([0u32; 1024]); // in PSRAM
//! let dma = mem::dma_buffer(4 * 1024); // in internal DRAM, DMA-safe
//! ```
//!
//! The raw marker allocators ([`ExternalMemory`] / [`InternalMemory`]) are also
//! re-exported as an escape hatch for `allocator_api2` containers, but they do
//! **not** perform the atomic check — reach for them only when you know what
//! you are placing in PSRAM.
//!
//! ## Enforced vs. documented caveats
//!
//! - **Atomics must not live in PSRAM.** *Enforced* on the checked path:
//! [`psram_box`] / [`psram_vec`] bound `T: PsramSafe`, so anything holding an
//! `Atomic*` (directly or transitively) fails to compile.
//! - **DMA from PSRAM:** the original ESP32 (Fire27) cannot DMA out of PSRAM.
//! *Guarded* by [`assert_dma_capable`] (a `debug_assert` on Fire27, a no-op on
//! CoreS3, which can DMA from PSRAM); use [`dma_buffer`] to get an
//! internal-DRAM buffer in the first place.
//! - **opt-level > 0:** *Enforced* at build time — enabling the `psram` feature
//! with `opt-level = 0` fails the build (see `build.rs`). PSRAM timing
//! calibration is unreliable unoptimized.
use Vec;
pub use ;
use PSRAM;
use ram;
use Box;
use MaybeUninit;
/// Heap size profile — selects the HIL-proven per-board DRAM region sizes for a
/// workload. The BSP owns the sizes so every binary gets the validated values;
/// pass the matching profile to [`init_heap`].
/// Register the global heap regions for `profile`, plus external PSRAM when
/// `psram` is `Some`, using the HIL-proven per-board sizes. Call once, right
/// after [`crate::board::init`] / `Board::split` and before any allocation.
///
/// This is the single place a binary sets up the heap — it never calls
/// `esp_alloc::heap_allocator!` itself. esp-alloc's global heap holds at most
/// three regions; each profile registers at most the reclaimed-ROM region, the
/// plain-DRAM region and the external PSRAM region — never a fourth (a 4th
/// `add_region` panics silently). Pass `None` for heap-only workloads
/// (e.g. [`HeapProfile::Lvgl`], or a board with no external RAM).
///
/// Registering PSRAM needs the `psram` feature; without it a `Some(_)` argument
/// is accepted but the external region is **not** added (the DRAM regions still
/// are).
/// Map the board's external PSRAM and add it to the global heap as an
/// [`ExternalMemory`] region.
///
/// The size is auto-detected. Returns the amount of external (PSRAM) heap free
/// immediately after registration, in bytes.
///
/// Call once, after [`esp_hal::init`]. Usually invoked for you by [`init_heap`]
/// when you pass `Some(psram)`; call it directly only if you manage the DRAM
/// regions yourself. Calling it more than once is unsound — the PSRAM
/// controller must only be initialized a single time.
/// A private PSRAM region carved off the global heap, plus the external bytes
/// registered with the global heap. Returned by [`psram_split`].
// `private` is a `&'static mut` to uninit memory, so `PsramSplit` cannot derive
// `Debug`; a manual impl prints the base/len/free a consumer wants when bringing
// this up on a new board (`log::info!("{:?}", split)`).
/// Why [`psram_split`] could not satisfy the request.
/// Map the board's external PSRAM, carve a **private** region off the base, and
/// register the remainder with the global heap.
///
/// The split-out counterpart of [`init_psram_heap`] (which maps *and* registers
/// the whole region globally in one step). Reach for `psram_split` when a
/// consumer needs a private, exclusive, contiguous PSRAM region to hand to a
/// *foreign* allocator — LVGL's built-in TLSF via `lv_mem_add_pool` — rather than
/// routing those allocations through the shared global allocator.
///
/// - `reserve: Some(n)` carves `n` bytes private from the base and registers the
/// remainder globally. `reserve: None` makes the whole region private (nothing
/// is added to the global heap; [`PsramSplit::global_free`] is `0`).
/// `reserve: Some(0)` is the mirror image: an empty [`PsramSplit::private`]
/// slice and *all* PSRAM registered globally — i.e. it degenerates to what
/// [`init_psram_heap`] does. Sound (a zero-length slice grants access to
/// nothing) but rarely what you want.
/// - The private region is carved **from the base**, so [`PsramSplit::private`]
/// starts at the (large-aligned) PSRAM mapping base — aligned for LVGL's TLSF
/// with no math; esp-alloc aligns the remainder's base internally.
/// - This primitive controls *placement* (the private/global split). The PSRAM
/// **hardware** mapping uses the default [`esp_hal::psram`] config, which
/// auto-detects size — the board-correct choice for both boards. A
/// `psram_split_with(config)` variant is a non-breaking addition if a consumer
/// ever needs a custom `PsramConfig` (a fixed `PsramSize`, say); no current
/// one does.
///
/// Call once, after [`esp_hal::init`], **instead of** passing `Some(psram)` to
/// [`init_heap`] or calling [`init_psram_heap`]: taking `PSRAM<'static>` by value
/// makes the once-only mapping a type-level guarantee, so the two cannot both run.
///
/// # Caveats handed back to the caller
/// - **No atomics in the private region.** The checked [`psram_box`] /
/// [`psram_vec`] cannot guard a foreign allocator, so keeping `Atomic*` out of
/// whatever is placed here is the caller's responsibility (holds for LVGL while
/// `LV_USE_OS` is `LV_OS_NONE`). See [`PsramSafe`].
/// - **DMA.** The ESP32 (Fire27) cannot DMA to/from PSRAM at all; the ESP32-S3
/// can but slowly. A foreign allocator must not place DMA'd buffers here. See
/// [`assert_dma_capable`] / [`dma_buffer`].
///
/// # Errors
/// [`PsramSplitError::NotMapped`] if PSRAM does not map; [`PsramSplitError::TooSmall`]
/// if it maps smaller than `reserve`.
/// Marker for types safe to store in PSRAM: nothing holding an *inline* atomic.
///
/// Atomic read-modify-write instructions misbehave against PSRAM-backed
/// addresses on ESP32 / ESP32-S3, so the checked allocators [`psram_box`] /
/// [`psram_vec`] only accept `T: PsramSafe`. Like `Send` / `Sync` this is an
/// auto trait: a struct is `PsramSafe` iff every field is, so a type that
/// embeds an `Atomic*` (directly or transitively — e.g. via `Arc`, many lock
/// types) is rejected at compile time.
///
/// A *pointer or reference* to an atomic living elsewhere is fine — the atomic
/// itself is not in PSRAM — so `&T`, `&mut T`, `*const T` and `*mut T` are
/// always `PsramSafe`.
///
/// # Safety
/// Only implement (or negative-impl) this to reflect the atomic-in-PSRAM
/// hazard; the checked allocators rely on it to keep atomics out of PSRAM.
pub unsafe auto
/// Allocate `value` in external PSRAM. Atomic-bearing `T` is rejected at
/// compile time via [`PsramSafe`].
/// A `Vec<T>` with room for `capacity` elements reserved in external PSRAM.
/// Atomic-bearing `T` is rejected at compile time via [`PsramSafe`].
/// Free bytes in the global heap's **internal** DRAM regions (reclaimed-ROM +
/// plain-DRAM), right now. The tight resource on both boards; use it to measure
/// headroom (e.g. before/after moving a subsystem's heap to PSRAM).
/// Free bytes in the global heap's **external** (PSRAM) region, right now — `0`
/// unless PSRAM was registered globally (via [`init_heap`] with `Some(psram)` /
/// [`init_psram_heap`]; a private [`psram_split`] region is *not* counted here).
/// A zeroed byte buffer in internal DRAM, suitable as a DMA buffer.
///
/// Convenience for the common "I need a DMA-capable scratch buffer" case so the
/// allocator does not have to be spelled out. The result is DMA-reachable on
/// both chips; pair it with [`assert_dma_capable`] if a buffer's origin is ever
/// in doubt.
/// Debug-assert that `buf` is DMA-reachable on this chip.
///
/// On the ESP32 (Fire27) the DMA engine cannot reach the PSRAM-mapped data
/// window, so a PSRAM-backed buffer handed to SPI/I2S DMA silently corrupts.
/// This catches that on first use under `debug_assertions`. It is a no-op
/// (compiled away) on the ESP32-S3, which *can* DMA from PSRAM.
/// No-op on every target except the ESP32 (Fire27); see the Fire27 variant.