MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
memoria_externa.h
Go to the documentation of this file.
1/* MMgr - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2 * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Commercial OR LicenseRef-Educational
3 *
4 * Every use falls under AGPL-3.0-or-later unless you hold explicit permission, which is either a
5 * negotiated commercial licensing contract or an educator's license issued to you personally.
6 */
7/**
8 * @file memoria_externa.h
9 * @brief Placement between internal and external memory, and a two-buffer index.
10 * @author dstroy0 (Douglas Quigg) <dquigg123@gmail.com>
11 * @date 2026-08-29
12 *
13 * @warning Everything below is declared only when MMGR_ENABLE_EXTRAM is set.
14 */
15#ifndef MMGR_MEMORIA_EXTERNA_H
16#define MMGR_MEMORIA_EXTERNA_H
17
18#include "mmgr.h"
19
20#if MMGR_ENABLE_EXTRAM
21
22EMBED_BEGIN_DECLS
23
24/**
25 * @brief Where a request should be placed.
26 *
27 * @note One byte on every compiler, through EMBED_ENUM_BYTE.
28 */
29EMBED_ENUM_BYTE(mmgr_place, PLACE_DRAM = 0, /**< Internal memory. */
30 PLACE_PSRAM = 1, /**< External memory. */
31 PLACE_FAIL = 2 /**< Neither will take it. */
32);
33
34/**
35 * @brief A pair of buffers, one being filled while the other is drained.
36 *
37 * @note fill_index is the only member. The buffers it indexes are stored elsewhere.
38 * @note The two roles are one bit. The drain index is this one's complement. A swap is a flip and
39 * there is no second member to keep in step.
40 * @warning What holds fill_index to 0 or 1 is mmgr_pingpong_init setting it and mmgr_pingpong_swap
41 * flipping it. A pair that has not been through mmgr_pingpong_init carries whatever its
42 * storage contained, and no call here tests the value.
43 */
44typedef struct
45{
46 uint8_t fill_index; /**< Index of the buffer being filled, 0 or 1. */
47} PingPong;
48
49/**
50 * @brief Arguments for every exter call, where each call reads only the members it needs.
51 *
52 * @note free_dram and free_psram are supplied by the caller and used as given.
53 * @note place reads the six figures. The pingpong entries read pingpong alone.
54 */
55typedef struct
56{
57 const size_t size; /**< Bytes to place. */
58 const embed_bool dma_required; /**< The bytes must be reachable by DMA. */
59 const size_t free_dram; /**< Bytes still free in internal memory. */
60 const size_t free_psram; /**< Bytes still free in external memory. */
61 const size_t psram_threshold; /**< Size at or above which external memory is tried first. */
62 const size_t dram_reserve; /**< Internal bytes that must remain free afterwards. */
63 PingPong *const pingpong; /**< Pair the pingpong entries act on [BORROWS]. */
65
66/**
67 * @brief Type of the exter dispatch table.
68 *
69 * @note EMBED_TABLE_LAYOUT asserts the five members sit at consecutive EMBED_FUNCTION_POINTER_BYTES offsets, with
70 * nothing else.
71 * @note Every entry takes the same argument pack, as in locus_carcerum and memoria_anularis.
72 */
73typedef struct
74{
75 mmgr_place (*place)(const ExternaCfg *args); /**< Decides where a request goes. */
76 void (*pingpong_init)(const ExternaCfg *args); /**< Points the pair at buffer 0. */
77 uint8_t (*pingpong_fill)(const ExternaCfg *args); /**< Index being filled. */
78 uint8_t (*pingpong_drain)(const ExternaCfg *args); /**< Index being drained. */
79 uint8_t (*pingpong_swap)(const ExternaCfg *args); /**< Swaps the two roles. */
81EMBED_TABLE_LAYOUT(MemoriaExternaNs, place, pingpong_init, pingpong_fill, pingpong_drain, pingpong_swap);
82
83/**
84 * @brief Decides whether a request belongs in internal or external memory.
85 *
86 * @param[in] args Request size, the DMA requirement and both memory figures [BORROWS].
87 * @return PLACE_DRAM, PLACE_PSRAM, or PLACE_FAIL when neither will take it.
88 * @note A size of 0 gives PLACE_FAIL.
89 * @note A DMA request only ever gives PLACE_DRAM or PLACE_FAIL, never external memory.
90 * @note At or above psram_threshold external memory is preferred, below it internal is.
91 * @warning The two tests are not the same shape. Internal placement must also leave dram_reserve
92 * free, where the external one is a size comparison alone.
93 */
94mmgr_place mmgr_extern_place(const ExternaCfg *args);
95
96/**
97 * @brief Points the pair at buffer 0.
98 *
99 * @param[in,out] args Pair to reset, as args->pingpong [BORROWS].
100 * @note The other three pingpong entries read a pair this call has set.
101 * @warning args->pingpong must not be null. Nothing checks it and no assertion covers it, and this
102 * call writes through it at once.
103 */
104void mmgr_pingpong_init(const ExternaCfg *args);
105
106/**
107 * @brief Returns the index of the buffer being filled.
108 *
109 * @param[in] args Pair to read, as args->pingpong [BORROWS].
110 * @return 0 or 1.
111 * @note Does not modify args->pingpong.
112 * @warning args->pingpong must not be null. Nothing checks it and no assertion covers it.
113 * @warning Reports fill_index as it stands. On a pair that has not been through mmgr_pingpong_init
114 * that is whatever its storage contained, and this call does not bound it to 0 or 1.
115 */
116uint8_t mmgr_pingpong_fill_index(const ExternaCfg *args);
117
118/**
119 * @brief Returns the index of the buffer being drained.
120 *
121 * @param[in] args Pair to read, as args->pingpong [BORROWS].
122 * @return The other index, 0 or 1.
123 * @note The answer is fill_index with its low bit flipped, and so the two indexes always disagree.
124 * @note Does not modify args->pingpong.
125 * @warning args->pingpong must not be null. Nothing checks it and no assertion covers it.
126 * @warning The answer is an index only while fill_index is 0 or 1. On a pair that has not been
127 * through mmgr_pingpong_init it is the flip of whatever its storage contained.
128 */
129uint8_t mmgr_pingpong_drain_index(const ExternaCfg *args);
130
131/**
132 * @brief Swaps which buffer is filled and which is drained.
133 *
134 * @param[in,out] args Pair to flip, as args->pingpong [BORROWS].
135 * @return The index now being filled, 0 or 1.
136 * @note Flips the low bit. A swap of a swap is where it started.
137 * @warning args->pingpong must not be null. Nothing checks it and no assertion covers it.
138 * @warning One caller at a time. The flip and the read that follows it are two steps on a plain byte,
139 * with no atomic and no lock, and so two callers swapping the same pair can come away holding
140 * the same index.
141 */
142uint8_t mmgr_pingpong_swap(const ExternaCfg *args);
143
144/**
145 * @brief Dispatch table instance named exter.
146 *
147 * @note pingpong_fill calls mmgr_pingpong_fill_index and pingpong_drain calls mmgr_pingpong_drain_index.
148 */
149EMBED_TABLE_STORAGE MemoriaExternaNs exter EMBED_UNUSED = {
150 .place = mmgr_extern_place,
151 .pingpong_init = mmgr_pingpong_init,
152 .pingpong_fill = mmgr_pingpong_fill_index,
153 .pingpong_drain = mmgr_pingpong_drain_index,
154 .pingpong_swap = mmgr_pingpong_swap,
155};
156
157EMBED_END_DECLS
158
159#endif
160
161#endif
static const MemoriaExternaNs exter
Dispatch table instance named exter.
mmgr_place mmgr_extern_place(const ExternaCfg *args)
Decides whether a request belongs in internal or external memory.
mmgr_place
Where a request should be placed.
The embedded_types headers every module builds on.
Arguments for every exter call, where each call reads only the members it needs.
const size_t size
const embed_bool dma_required
PingPong *const pingpong
const size_t psram_threshold
const size_t dram_reserve
const size_t free_psram
const size_t free_dram
Type of the exter dispatch table.
uint8_t(*) pingpong_fill(const ExternaCfg *args)
void(*) pingpong_init(const ExternaCfg *args)
uint8_t(*) pingpong_drain(const ExternaCfg *args)
mmgr_place(*) place(const ExternaCfg *args)
uint8_t(*) pingpong_swap(const ExternaCfg *args)
A pair of buffers, one being filled while the other is drained.
uint8_t fill_index