MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
praet_tabula_vexillorum.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 praet_tabula_vexillorum.h
9 * @brief Every bit of the channel flag word, and the assertions that keep the map from drifting.
10 * @author dstroy0 (Douglas Quigg) <dquigg123@gmail.com>
11 * @date 2026-09-01
12 *
13 * @note The map is ours, and for that reason it needs checking. Nothing outside this library assigns
14 * these bits. A status added at the wrong offset or a region moved one byte down is a change
15 * no compiler would question and no test would necessarily reach. Placing one status on top of
16 * the region descriptor already happened once here.
17 * @note Statuses are named by token id and their bits are derived from it. A name and its bit
18 * cannot disagree. What the assertions below check is the id set: that it is dense, that no two
19 * ids collide, and that the three fields do not overlap each other.
20 * @note The map is the same on every build. A status a build never sets still owns its bit, because a
21 * flag word read on one part has to mean what it means on another.
22 */
23#ifndef MMGR_PRAET_TABULA_VEXILLORUM_H
24#define MMGR_PRAET_TABULA_VEXILLORUM_H
25
26#include "mmgr.h"
27
28EMBED_BEGIN_DECLS
29
30/**
31 * @brief Detached: the channel holds no engine and no vector.
32 */
33#define PRAET_SEPARATUS 0u
34
35/**
36 * @brief Attached: the channel holds the engine and will take a transfer.
37 */
38#define PRAET_ADNEXUS 1u
39
40/**
41 * @brief Busy: the engine is moving bytes on this channel.
42 *
43 * @note The DMA busy, not the reader/setter's lock. The two are separate concerns and the lock lives
44 * in its own volatile.
45 */
46#define PRAET_BUSY 2u
47
48/**
49 * @brief Ok: the last transfer finished and the channel is still attached.
50 *
51 * @note The transfer's result, not the channel's health. A channel that failed carries PRAET_ERROR
52 * alongside, and so the two are separate bits.
53 */
54#define PRAET_OK 3u
55
56/**
57 * @brief Bits the quaternary core occupies, at the bottom of the word.
58 */
59#define PRAET_CORE_BITS 2u
60
61/**
62 * @brief The two bits the quaternary core occupies.
63 *
64 * @note The core sits at the bottom with every status above it, and so reading the state is a mask and
65 * reading a status is a test. Nothing shifts.
66 */
67#define PRAET_CORE_MASK ((uint32_t)((1u << PRAET_CORE_BITS) - 1u))
68
69/**
70 * @brief Token id of the status set while the channel holds an interrupt vector.
71 */
72#define PRAET_ID_CLAIMED 0u
73
74/**
75 * @brief Token id of the status set while the engine is settling.
76 */
77#define PRAET_ID_SETTLING 1u
78
79/**
80 * @brief Token id of the status set while a detach is asked for and not finished.
81 */
82#define PRAET_ID_SEPARANS 2u
83
84/**
85 * @brief Token id of the status set where the last transfer ended in an error.
86 */
87#define PRAET_ID_ERROR 3u
88
89/**
90 * @brief Token id of the status set where a running channel went unkicked for a whole window.
91 */
92#define PRAET_ID_STALLED 4u
93
94/**
95 * @brief Token id of the status set where a stalled transfer was backed out.
96 */
97#define PRAET_ID_ABANDONED 5u
98
99/**
100 * @brief Token id of the status set where a stalled transfer's bytes were treated as unsafe.
101 */
102#define PRAET_ID_SCRUBBED 6u
103
104/**
105 * @brief Token id of the status set where the boundary word was measured during a recovery.
106 */
107#define PRAET_ID_MEASURED 7u
108
109/**
110 * @brief How many status token ids there are.
111 *
112 * @note Raise this when a status is added, and the assertion on PRAET_EVERY_STATUS fails until the
113 * new one is listed there too.
114 */
115#define PRAET_STATUS_COUNT 8u
116
117/**
118 * @brief First bit the statuses occupy, the bit above the core.
119 */
120#define PRAET_STATUS_SHIFT PRAET_CORE_BITS
121
122/**
123 * @brief The bit a status token id owns.
124 *
125 * @param[in] id_ Status token id, below PRAET_STATUS_COUNT.
126 * @return That id's bit, placed above the core.
127 * @note Every status below is defined through this, and so no status carries a hand-written bit that
128 * could disagree with the id beside it.
129 */
130#define PRAET_STATUS_BIT(id_) ((uint32_t)1u << (PRAET_STATUS_SHIFT + (id_)))
131
132/**
133 * @brief Every bit the statuses occupy, derived from how many there are.
134 */
135#define PRAET_STATUS_MASK ((uint32_t)(((1u << PRAET_STATUS_COUNT) - 1u) << PRAET_STATUS_SHIFT))
136
137/**
138 * @brief Set while the channel holds an interrupt vector.
139 *
140 * @note Claimed at attach and released at detach. There is no reason to hold a vector for a memory
141 * mover that is not moving anything, and the claim gives the context one owner.
142 */
143#define PRAET_CLAIMED PRAET_STATUS_BIT(PRAET_ID_CLAIMED)
144
145/**
146 * @brief Set while the engine is settling and will take no transfer.
147 */
148#define PRAET_SETTLING PRAET_STATUS_BIT(PRAET_ID_SETTLING)
149
150/**
151 * @brief Set where a detach has been asked for and the channel has not finished tearing down.
152 */
153#define PRAET_SEPARANS PRAET_STATUS_BIT(PRAET_ID_SEPARANS)
154
155/**
156 * @brief Set where the last transfer ended in an error, clear where it completed.
157 */
158#define PRAET_ERROR PRAET_STATUS_BIT(PRAET_ID_ERROR)
159
160/**
161 * @brief Set where a running channel went a whole keepalive window without being kicked.
162 *
163 * @note Says the engine stopped moving, and says nothing about whether the transfer finished. A
164 * stalled channel is still busy, because nothing has told this library otherwise.
165 */
166#define PRAET_STALLED PRAET_STATUS_BIT(PRAET_ID_STALLED)
167
168/**
169 * @brief Set where a stalled transfer was backed out and its bytes left as they were.
170 *
171 * @note Owns its bit on every build. A build with recovery off never sets it, and the bit stays
172 * reserved to keep a flag word meaning one thing everywhere.
173 */
174#define PRAET_ABANDONED PRAET_STATUS_BIT(PRAET_ID_ABANDONED)
175
176/**
177 * @brief Set where a stalled transfer was resolved by treating its bytes as unsafe.
178 *
179 * @note The bytes are the caller's and this context has no pointer to them, and so nothing here scrubs
180 * anything. The flag records the decision; the caller zeroes its own storage.
181 */
182#define PRAET_SCRUBBED PRAET_STATUS_BIT(PRAET_ID_SCRUBBED)
183
184/**
185 * @brief Set where a recovery measured the word the engine was in the middle of.
186 *
187 * @note What tells a reader that the boundary checksum means anything. A checksum of zero is a legal
188 * one. A reader that took the value alone could not tell a measured word from an unmeasured
189 * context.
190 * @note A context whose declaration chose the DISABLE token never sets this, and neither does a
191 * recovery whose sample already landed on a word boundary - there is no partial word to
192 * measure.
193 */
194#define PRAET_MEASURED PRAET_STATUS_BIT(PRAET_ID_MEASURED)
195
196/**
197 * @brief Every status, listed by name.
198 *
199 * @note Written out by hand, because this is the half of the check that has to come from
200 * somewhere other than PRAET_STATUS_COUNT. Comparing a list of names against a count neither
201 * one produced is what catches a duplicated id or a status nobody placed.
202 */
203#define PRAET_EVERY_STATUS \
204 (PRAET_CLAIMED | PRAET_SETTLING | PRAET_SEPARANS | PRAET_ERROR | PRAET_STALLED | PRAET_ABANDONED | \
205 PRAET_SCRUBBED | PRAET_MEASURED)
206
207/**
208 * @brief First bit of the region descriptor.
209 *
210 * @note A byte on a byte boundary. The descriptor is a number derived from the address space that
211 * the region is inferred from, and so nothing here stores an address. Placing it at a byte edge
212 * lets a build that wants to compress it to eight bits do so without moving a status.
213 * @note The third byte and not the second. Statuses grow upward from the core and the region has to
214 * start above all of them - at bit 8 it shared a bit with PRAET_SCRUBBED, which reads as a
215 * region whose low bit flips when a transfer is scrubbed.
216 */
217#define PRAET_REGION_SHIFT 16u
218
219/**
220 * @brief How wide the region descriptor is.
221 */
222#define PRAET_REGION_BITS 8u
223
224/**
225 * @brief The largest descriptor the region field holds.
226 */
227#define PRAET_REGION_MAX ((uint32_t)((1u << PRAET_REGION_BITS) - 1u))
228
229/**
230 * @brief The bits the region descriptor occupies once shifted into place.
231 */
232#define PRAET_REGION_MASK (PRAET_REGION_MAX << PRAET_REGION_SHIFT)
233
234/**
235 * @brief A region descriptor placed in the field the map gives it.
236 *
237 * @param[in] region_ Region descriptor, narrowed to what the field holds.
238 * @return region_ in position, and never a bit outside PRAET_REGION_MASK.
239 * @note The one way to write the field. Narrowing at the write site instead would put the field's
240 * width in two places, and the copy that stops matching is the single one nothing checks.
241 */
242#define PRAET_REGION_FIELD(region_) (((uint32_t)(region_) & PRAET_REGION_MAX) << PRAET_REGION_SHIFT)
243
244/**
245 * @brief The regions a channel can be attached over.
246 *
247 * @note Token ids, the same way the statuses above are token ids. A region arrives at an attach as one
248 * of these names. A mistyped one is an undeclared identifier carrying the name that was
249 * written, and a caller cannot pass a byte that means nothing.
250 * @note Two of them, because ParsMemoriaeInternae and ParsMemoriaeExternum are the two declarations a
251 * pool can be written with. A part with more address spaces than that adds ids here and the
252 * assertion below keeps them inside the field.
253 * @note The values are ordinals. A build wanting a descriptor it can infer an address space from
254 * replaces the numbers and changes nothing else, because the field is a byte and everything
255 * reading it goes through PRAET_REGION_FIELD.
256 */
257typedef enum
258{
259 PRAET_REGION_INTERNAL = 0,
260 PRAET_REGION_EXTERNAL = 1
262
263/**
264 * @brief The largest region token id there is.
265 *
266 * @note Raise this when a region is added, and the assertion below fails until the field is wide
267 * enough to hold it.
268 */
269#define PRAET_REGION_ID_HIGHEST PRAET_REGION_EXTERNAL
270
271/**
272 * @brief Every bit the map accounts for.
273 *
274 * @note What a flag word is allowed to carry. A build whose writes stay inside this cannot produce a
275 * word with a bit nobody named, and so nothing has to look at a word at run time to find
276 * that out.
277 */
278#define PRAET_MAP_MASK (PRAET_CORE_MASK | PRAET_STATUS_MASK | PRAET_REGION_MASK)
279
280// Every value the core mask can hold has a name, and the four names are distinct. Setting one bit per
281// core value turns both halves of that into one comparison
282EMBED_STATIC_ASSERT(((1u << PRAET_SEPARATUS) | (1u << PRAET_ADNEXUS) | (1u << PRAET_BUSY) | (1u << PRAET_OK)) ==
283 ((1u << (PRAET_CORE_MASK + 1u)) - 1u),
284 "the four core states are not exactly the values PRAET_CORE_MASK holds");
285
286// The id set is dense, nothing collides, and nothing is missing. A duplicated id drops a bit out of
287// the list; an id at or above the count puts one outside the mask; a status left out of
288// PRAET_EVERY_STATUS leaves a hole. All three come out as this one inequality
289EMBED_STATIC_ASSERT(PRAET_EVERY_STATUS == PRAET_STATUS_MASK,
290 "the status token ids are not dense and distinct, or PRAET_EVERY_STATUS is missing one");
291
292EMBED_STATIC_ASSERT((PRAET_CORE_MASK & PRAET_STATUS_MASK) == 0u, "a status sits on top of the quaternary core");
293
294EMBED_STATIC_ASSERT((PRAET_STATUS_MASK & PRAET_REGION_MASK) == 0u, "a status sits on top of the region descriptor");
295
296EMBED_STATIC_ASSERT((PRAET_CORE_MASK & PRAET_REGION_MASK) == 0u,
297 "the region descriptor sits on top of the quaternary core");
298
300 "the region descriptor starts below the highest status");
301
302// A descriptor a build wants to read or write as a byte has to be addressable as one
303EMBED_STATIC_ASSERT((PRAET_REGION_SHIFT % 8u) == 0u, "the region descriptor is not on a byte boundary");
304
305EMBED_STATIC_ASSERT((PRAET_REGION_SHIFT + PRAET_REGION_BITS) <= 32u,
306 "the map runs off the top of the 32 bit flag word");
307
308// The write reaches the whole field and never a bit past it, whatever the caller hands over. Both
309// halves matter: a field that cannot be filled loses descriptors, and one that overruns lands in a
310// status
312 "PRAET_REGION_FIELD cannot fill the field the map gives it");
313
314EMBED_STATIC_ASSERT((PRAET_REGION_FIELD(0xFFFFFFFFu) & (uint32_t)~PRAET_REGION_MASK) == 0u,
315 "PRAET_REGION_FIELD writes outside the region descriptor");
316
317// Nothing a flag word carries sits outside the map, and the map has nothing in it that no field
318// claims. A word is one of these three things end to end
320 "the map is not exactly the core, the statuses and the region");
321
322// A region token id that does not fit the field would be written truncated, and the channel would
323// report a region nobody asked for
324EMBED_STATIC_ASSERT((uint32_t)PRAET_REGION_ID_HIGHEST <= PRAET_REGION_MAX,
325 "there are more region token ids than the region descriptor field can hold");
326
327EMBED_END_DECLS
328
329#endif
The embedded_types headers every module builds on.
PraetRegion
The regions a channel can be attached over.
#define PRAET_STATUS_MASK
Every bit the statuses occupy, derived from how many there are.
#define PRAET_SEPARATUS
Detached: the channel holds no engine and no vector.
#define PRAET_BUSY
Busy: the engine is moving bytes on this channel.
#define PRAET_MAP_MASK
Every bit the map accounts for.
#define PRAET_OK
Ok: the last transfer finished and the channel is still attached.
#define PRAET_CORE_MASK
The two bits the quaternary core occupies.
#define PRAET_REGION_FIELD(region_)
A region descriptor placed in the field the map gives it.
#define PRAET_STATUS_COUNT
How many status token ids there are.
#define PRAET_REGION_BITS
How wide the region descriptor is.
#define PRAET_ADNEXUS
Attached: the channel holds the engine and will take a transfer.
#define PRAET_EVERY_STATUS
Every status, listed by name.
#define PRAET_REGION_MAX
The largest descriptor the region field holds.
#define PRAET_REGION_SHIFT
First bit of the region descriptor.
#define PRAET_REGION_MASK
The bits the region descriptor occupies once shifted into place.
#define PRAET_STATUS_SHIFT
First bit the statuses occupy, the bit above the core.
#define PRAET_REGION_ID_HIGHEST
The largest region token id there is.