MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
praet_descriptor.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_descriptor.h
9 * @brief A transfer written down: where from, where to, how many bytes, how each address moves, and
10 * what runs after it.
11 * @author dstroy0 (Douglas Quigg) <dquigg123@gmail.com>
12 * @date 2026-09-01
13 *
14 * @note The shape three vendors already agree on. An ESP32 GDMA dma_descriptor_t, an i.MX RT eDMA
15 * TCD and a Zephyr dma_block_config each carry where from, where to, how much, how the
16 * addresses move, who holds it, and what is next. Three architectures, one descriptor.
17 * @note Descriptors link, and that is where the arrangements come from. Two whose next point at each
18 * other are ping-pong. One pointing at itself is circular. A chain is scatter-gather. A next of
19 * NULL is one shot. No part has a ping-pong feature and neither does this.
20 * @note Who holds a descriptor is per descriptor and not per channel. A channel's own state is the
21 * quaternary core in its flag word, and that answers a different question: the core says what
22 * the channel is doing, and an owner says which link in the chain the engine is on.
23 * @note A peripheral end is an address that does not advance. Where that address is comes from the
24 * vendor's own header, which the compiler has already read. A caller names theirs and no
25 * number of any part appears in this tree.
26 */
27#ifndef MMGR_PRAET_DESCRIPTOR_H
28#define MMGR_PRAET_DESCRIPTOR_H
29
31
32EMBED_BEGIN_DECLS
33
34/**
35 * @brief How an address behaves as a transfer runs.
36 *
37 * @note The one fact memcpy does not need. Memory to memory advances both sides; a peripheral is an
38 * address that stays where it is, and which side stays put makes a transfer a read or a
39 * write.
40 * @note Token ids, the same as the statuses and the regions. A mistyped name is an undeclared
41 * identifier carrying the name that was written.
42 */
43typedef enum
44{
45 PRAET_ADDRESS_ADVANCES = 0, /**< The address moves on by the transfer width each step. */
46 PRAET_ADDRESS_FIXED = 1 /**< The address stays where it is, as a peripheral is. */
48
49/**
50 * @brief Bytes the engine moves in one step, the measure everything else is counted in.
51 *
52 * @note Named in full for what it is. A transfer's offsets, its length and the boundary a recovery
53 * rounds to are all counted in this, and so it is the measure and not a property beside the others.
54 * @note Carries the count itself, and so the assertions below divide a length by it
55 * and test an offset against it with no table in between.
56 * @note octetus and verbum are the tree's own words for a byte and a word, as in
57 * octetus_introitus_exitus and verba_scribo.
58 */
59typedef enum
60{
61 PRAET_MENSURA_OCTETUS = 1, /**< One byte a step. */
62 PRAET_MENSURA_SEMIVERBUM = 2, /**< Half a word a step. */
63 PRAET_MENSURA_VERBUM = 4 /**< A whole word a step. */
65
66/**
67 * @brief The largest measure this module knows about.
68 *
69 * @note Raise this when a wider step is added, and the assertion on a descriptor's measure
70 * stops one being written that nothing here can carry.
71 */
72#define PRAET_MENSURA_MAXIMA PRAET_MENSURA_VERBUM
73
74/**
75 * @brief Which way a transfer walks the words it moves.
76 *
77 * @note Named in full, because a bare direction says nothing about what is moving in it. This is the
78 * direction of the movement of the word, and the word is what the engine steps by.
79 * @note What memor already answers for a copy, and for the same reason. Where the two ends overlap,
80 * one direction reads words the other has already written over. Where they do not, neither
81 * direction is wrong and the engine takes whichever it likes.
82 * @note Settled once and then not asked again. Here it is settled before anything runs, because both
83 * ends of a descriptor come from a declaration.
84 */
85typedef enum
86{
87 PRAET_MOTUS_NEUTRUM = 0, /**< Neither way. The ends do not overlap, and so nothing is at risk. */
88 PRAET_MOTUS_SURSUM = 1, /**< Upward, from the low word on, which is safe writing downward. */
89 PRAET_MOTUS_DEORSUM = 2 /**< Downward, from the high word back, which is safe writing upward. */
91
92/**
93 * @brief The direction of the movement of the word, for two spans of one pool.
94 *
95 * @param from_ Bytes into the pool the read starts at.
96 * @param to_ Bytes into the pool the write starts at.
97 * @param bytes_ Bytes moved.
98 * @return A DirectioMotusVerbi.
99 * @note Integers, and so this is answered while compiling. Both spans are offsets into one declared
100 * object, and where they came from makes them comparable at all: two addresses handed
101 * in as pointers could not be ordered at compile time, and comparing pointers into separate
102 * objects is not something the language defines.
103 * @note Writing upward over a source means the high words must move first, and so the walk goes downward.
104 * Writing downward is the mirror of it. Spans that start at the same place move nothing over
105 * anything and take neither.
106 */
107#define PRAET_DIRECTIO_MOTUS_VERBI_FOR(from_, to_, bytes_) \
108 ((((from_) < ((to_) + (bytes_))) && ((to_) < ((from_) + (bytes_))) && ((to_) != (from_))) \
109 ? (((to_) > (from_)) ? PRAET_MOTUS_DEORSUM : PRAET_MOTUS_SURSUM) \
110 : PRAET_MOTUS_NEUTRUM)
111
112/**
113 * @brief Who holds a descriptor.
114 *
115 * @note Per descriptor. An ESP32 descriptor calls this owner, an eDMA carries DONE and ACTIVE in its
116 * control word, and a Zephyr block config calls it busy. They are the same fact.
117 */
118typedef enum
119{
120 PRAET_OWNER_SOFTWARE = 0, /**< The program may read it and write it. */
121 PRAET_OWNER_ENGINE = 1 /**< The engine is working from it,, alone may touch it. */
123
124/**
125 * @brief One transfer, written down.
126 *
127 * @param source First byte read [BORROWS].
128 * @param destination First byte written [BORROWS].
129 * @param bytes How many bytes the transfer moves.
130 * @param next The descriptor that runs after this one, or NULL where nothing does
131 * [BORROWS].
132 * @param source_addressing Whether the source address advances, as a PraetAddressing.
133 * @param destination_addressing Whether the destination address advances.
134 * @param ego_sum_mensura Bytes moved in one step, as an EgoSumMensura. Every offset, every
135 * length and the boundary a recovery rounds to are counted in it.
136 * @param directio_motus_verbi Which way the words are walked, as a DirectioMotusVerbi.
137 * @param owner Who holds it, as a PraetDescriptorOwner.
138 * @note Every field except owner is settled when the descriptor is declared. A declaration emits it
139 * as initialized data, and so nothing runs to make one usable.
140 * @warning source and destination belong to whoever declared the pools they came from, and this holds
141 * neither. The declarator is what proves those pools exist and that the spans fit inside
142 * them.
143 */
144typedef struct PraetDescriptor PraetDescriptor;
145
147{
148 const uint8_t *source;
149 uint8_t *destination;
150 embed_word bytes;
151 PraetDescriptor *next;
152 uint8_t source_addressing;
153 uint8_t destination_addressing;
154 uint8_t ego_sum_mensura;
155 uint8_t directio_motus_verbi;
156 uint8_t owner;
157};
158
159/**
160 * @brief Fails the build where one end of a transfer does not sit whole steps inside its pool.
161 *
162 * @param what_ Text naming which end this is, for the message.
163 * @param pool_ Pool declared by ParsMemoriaeInternae or ParsMemoriaeExternum.
164 * @param offset_ Bytes into that pool the span starts at.
165 * @param bytes_ Bytes in the span.
166 * @param mensura_ Bytes the engine moves in one step.
167 * @note Three conditions in one assertion, because they fail for one reason and a caller fixing one
168 * wants to see the others. The span fits the pool, the offset lands on a step boundary, and the
169 * length is a whole number of steps.
170 * @note Static assertions, where PraetSubmit uses the expression form. A declarator is at file scope
171 * and a bare expression cannot sit there. Naming the end in the message is what
172 * replaces the member name the expression form prints.
173 * @warning Reads sizeof(mmgr_pars_storage_##pool_), and so it holds for a span whose offset, length and
174 * width are known while compiling. Anything computed at run time is not bounded by this and
175 * is not bounded anywhere else.
176 */
177#define PRAET_STEPS_FIT(what_, pool_, offset_, bytes_, mensura_) \
178 EMBED_STATIC_ASSERT(((offset_) + (bytes_)) <= sizeof(mmgr_pars_storage_##pool_), \
179 what_ " runs past the end of " #pool_); \
180 EMBED_STATIC_ASSERT(((offset_) % (mensura_)) == 0u, what_ " starts part way into a step of " #pool_); \
181 EMBED_STATIC_ASSERT(((bytes_) % (mensura_)) == 0u, what_ " is not a whole number of steps")
182
183/**
184 * @brief Declares one memory to memory transfer, and what runs after it.
185 *
186 * @param name_ Name the descriptor is reached by.
187 * @param from_pool_ Pool the bytes are read from.
188 * @param from_offset_ Bytes into that pool the read starts at.
189 * @param to_pool_ Pool the bytes are written to.
190 * @param to_offset_ Bytes into that pool the write starts at.
191 * @param bytes_ Bytes to move.
192 * @param mensura_ Bytes moved in one step, as a EgoSumMensura.
193 * @param next_ The descriptor that runs after this one, or NULL.
194 * @note Both pools are named, and so both spans are proved to fit before anything runs and both addresses
195 * come from a declaration, with no caller stating one. That is the same check
196 * PraetAttach makes, applied to each end.
197 * @note Both addresses advance, as memory to memory means. A side that stays put is a
198 * peripheral, and naming one needs an address this has no way to take.
199 * @note Emitted as initialized data with the owner in software's hands. A descriptor the engine has
200 * not been given is one the program may still write.
201 * @note Two different pools are two separately declared objects, and so they cannot overlap and the
202 * direction is settled as either. Naming one pool at both ends collides on the enumerators this
203 * emits, because the two carry the same prefix and differ only by the pool. A transfer inside
204 * one pool is PraetDescriptorWithin, which has offsets it can compare.
205 * @warning direction_ is stated here and derived by the two macros above this one. Reaching this
206 * directly with the wrong answer is a transfer that reads bytes it has already written over.
207 */
208#define PRAET_DESCRIPTOR_BODY(name_, source_, source_mode_, dest_, dest_mode_, bytes_, mensura_, motus_, next_) \
209 EMBED_STATIC_ASSERT((mensura_) <= (int)PRAET_MENSURA_MAXIMA, \
210 #name_ " asks for a step wider than anything this module carries"); \
211 EMBED_STATIC_ASSERT(((mensura_) & ((mensura_) - 1)) == 0, #name_ " asks for a step that is not a power of two"); \
212 static PraetDescriptor name_ EMBED_UNUSED = { \
213 .source = (source_), \
214 .destination = (dest_), \
215 .bytes = (embed_word)(bytes_), \
216 .next = (next_), \
217 .source_addressing = (uint8_t)(source_mode_), \
218 .destination_addressing = (uint8_t)(dest_mode_), \
219 .ego_sum_mensura = (uint8_t)(mensura_), \
220 .directio_motus_verbi = (uint8_t)(motus_), \
221 .owner = (uint8_t)PRAET_OWNER_SOFTWARE, \
222 }
223
224/**
225 * @brief Declares one memory to memory transfer, and what runs after it.
226 *
227 * @param name_ Name the descriptor is reached by.
228 * @param from_pool_ Pool the bytes are read from.
229 * @param from_offset_ Bytes into that pool the read starts at.
230 * @param to_pool_ Pool the bytes are written to.
231 * @param to_offset_ Bytes into that pool the write starts at.
232 * @param bytes_ Bytes to move.
233 * @param mensura_ Bytes moved in one step, as a EgoSumMensura.
234 * @param motus_ Which way the words are walked, as a DirectioMotusVerbi.
235 * @param next_ The descriptor that runs after this one, or NULL.
236 */
237#define PraetDescriptorDeclare(name_, from_pool_, from_offset_, to_pool_, to_offset_, bytes_, mensura_, motus_, next_) \
238 PRAET_STEPS_FIT(#name_ " reading from " #from_pool_, from_pool_, from_offset_, bytes_, mensura_); \
239 PRAET_STEPS_FIT(#name_ " writing to " #to_pool_, to_pool_, to_offset_, bytes_, mensura_); \
240 PRAET_DESCRIPTOR_BODY(name_, &mmgr_pars_storage_##from_pool_[from_offset_], PRAET_ADDRESS_ADVANCES, \
241 &mmgr_pars_storage_##to_pool_[to_offset_], PRAET_ADDRESS_ADVANCES, bytes_, mensura_, motus_, \
242 next_)
243
244/**
245 * @brief Fails the build where a transfer between two pools names one pool twice.
246 *
247 * @param name_ Name of the descriptor this belongs to.
248 * @param from_pool_ Pool the bytes are read from.
249 * @param to_pool_ Pool the bytes are written to.
250 * @note Two enumerators carrying one prefix and differing only by the pool, and so naming one pool at
251 * both ends declares the same enumerator twice in one enum and the compiler refuses it. The
252 * same idiom MMGR_PARS_CLAIMED_ONCE uses, applied to a pair.
253 * @note Worth refusing because the direction a transfer between two pools takes is settled as either,
254 * and that answer is only true because two declared pools are two objects. One pool at both
255 * ends can overlap itself, and PraetDescriptorWithin is what has the offsets to work that out.
256 */
257#define PRAET_DESCRIPTOR_ENDS_DIFFER(name_, from_pool_, to_pool_) \
258 enum \
259 { \
260 name_##_end_##from_pool_ = 1, \
261 name_##_end_##to_pool_ = 2 \
262 }
263
264/**
265 * @brief Declares one transfer that runs once and stops.
266 *
267 * @param name_ Name the descriptor is reached by.
268 * @param from_pool_ Pool the bytes are read from.
269 * @param from_offset_ Bytes into that pool the read starts at.
270 * @param to_pool_ Pool the bytes are written to.
271 * @param to_offset_ Bytes into that pool the write starts at.
272 * @param bytes_ Bytes to move.
273 * @param mensura_ Bytes moved in one step.
274 * @note A next of NULL, the entirety of of what makes a transfer one shot.
275 */
276#define PraetOneShot(name_, from_pool_, from_offset_, to_pool_, to_offset_, bytes_, mensura_) \
277 PRAET_DESCRIPTOR_ENDS_DIFFER(name_, from_pool_, to_pool_); \
278 PraetDescriptorDeclare(name_, from_pool_, from_offset_, to_pool_, to_offset_, bytes_, mensura_, \
279 PRAET_MOTUS_NEUTRUM, NULL)
280
281/**
282 * @brief Declares one transfer that moves bytes inside a single pool, and works out which way.
283 *
284 * @param name_ Name the descriptor is reached by.
285 * @param pool_ Pool both ends sit in.
286 * @param from_offset_ Bytes into that pool the read starts at.
287 * @param to_offset_ Bytes into that pool the write starts at.
288 * @param bytes_ Bytes to move.
289 * @param mensura_ Bytes moved in one step, as a EgoSumMensura.
290 * @param next_ The descriptor that runs after this one, or NULL.
291 * @note Where the two spans overlap, the direction keeps the transfer from reading bytes it
292 * has already written over. It is worked out here from the offsets, once, and never asked
293 * again: a run of this descriptor has the answer in front of it.
294 * @note Both spans are offsets into one declared object, and that leaves them comparable at all.
295 * The same question about two pointers could not be answered while compiling, and comparing
296 * pointers into separate objects is not something the language defines.
297 */
298#define PraetDescriptorWithin(name_, pool_, from_offset_, to_offset_, bytes_, mensura_, next_) \
299 PraetDescriptorDeclare(name_, pool_, from_offset_, pool_, to_offset_, bytes_, mensura_, \
300 PRAET_DIRECTIO_MOTUS_VERBI_FOR(from_offset_, to_offset_, bytes_), next_)
301
302/**
303 * @brief Declares one transfer that runs itself again, forever.
304 *
305 * @param name_ Name the descriptor is reached by.
306 * @param from_pool_ Pool the bytes are read from.
307 * @param from_offset_ Bytes into that pool the read starts at.
308 * @param to_pool_ Pool the bytes are written to.
309 * @param to_offset_ Bytes into that pool the write starts at.
310 * @param bytes_ Bytes to move.
311 * @param mensura_ Bytes moved in one step.
312 * @note A descriptor whose next is itself. This defines circular, and no part has a feature
313 * called that either.
314 * @note The forward declaration ahead of it is a tentative definition, which C merges with the real
315 * one below. It lets the initializer name an address the declaration is still writing.
316 */
317#define PraetCircular(name_, from_pool_, from_offset_, to_pool_, to_offset_, bytes_, mensura_) \
318 PRAET_DESCRIPTOR_ENDS_DIFFER(name_, from_pool_, to_pool_); \
319 static PraetDescriptor name_; \
320 PraetDescriptorDeclare(name_, from_pool_, from_offset_, to_pool_, to_offset_, bytes_, mensura_, \
321 PRAET_MOTUS_NEUTRUM, &name_)
322
323/**
324 * @brief Declares two transfers that hand off to each other, forever.
325 *
326 * @param first_ Name the first descriptor is reached by.
327 * @param second_ Name the second descriptor is reached by.
328 * @param from_pool_ Pool both transfers read from.
329 * @param to_pool_ Pool both transfers write to.
330 * @param first_from_ Bytes into the source pool the first transfer reads at.
331 * @param first_to_ Bytes into the destination pool the first transfer writes at.
332 * @param second_from_ Bytes into the source pool the second transfer reads at.
333 * @param second_to_ Bytes into the destination pool the second transfer writes at.
334 * @param bytes_ Bytes each transfer moves.
335 * @param mensura_ Bytes moved in one step.
336 * @note Ping-pong, and it is two descriptors whose next point at each other. Nothing here is a mode
337 * and no part has a bit for it. What makes it double buffering is that the two write different
338 * halves, the caller's to arrange by the offsets they pass.
339 * @note Both spans are proved against their pools separately. A pair whose halves overlap or run
340 * off the end fails at the declaration that wrote it.
341 */
342#define PraetPingPong(first_, second_, from_pool_, to_pool_, first_from_, first_to_, second_from_, second_to_, bytes_, \
343 mensura_) \
344 PRAET_DESCRIPTOR_ENDS_DIFFER(first_, from_pool_, to_pool_); \
345 PRAET_DESCRIPTOR_ENDS_DIFFER(second_, from_pool_, to_pool_); \
346 static PraetDescriptor second_; \
347 PraetDescriptorDeclare(first_, from_pool_, first_from_, to_pool_, first_to_, bytes_, mensura_, \
348 PRAET_MOTUS_NEUTRUM, &second_); \
349 PraetDescriptorDeclare(second_, from_pool_, second_from_, to_pool_, second_to_, bytes_, mensura_, \
350 PRAET_MOTUS_NEUTRUM, &first_)
351
352/**
353 * @brief Declares one peripheral register a transfer may reach.
354 *
355 * @param name_ Name the peripheral is reached by.
356 * @param address_ Where the register is, as the vendor's own header gives it.
357 * @note The address is not this library's to know and it is not the caller's to invent. Every part
358 * ships a header naming its registers, the compiler has read it, and what a caller writes here
359 * is that name. An ESP32 reaches one through its GDMA peripheral struct, an i.MX RT through
360 * LPUART1->DATA, and neither number appears in this tree.
361 * @note Emits a pointer under a mangled name and an enumerator carrying the peripheral's. A
362 * descriptor naming a peripheral pastes onto both. Anything that was not declared here fails
363 * on a name nobody wrote. The same proof a pool gives, for a thing that has no extent.
364 * @note volatile, because a register is read and written by something other than this program and a
365 * compiler that cached one would be reading a value the part has moved on from.
366 * @warning No extent, the point. A peripheral is one address that stays where it is, and so
367 * there is nothing for a span check to measure and a transfer's length is bounded by the
368 * pool at its other end alone.
369 */
370#define PraetPeripheralDeclare(name_, address_) \
371 static volatile uint8_t *const mmgr_praet_peripheral_##name_ EMBED_UNUSED = (volatile uint8_t *)(address_); \
372 enum \
373 { \
374 name_##_is_a_declared_peripheral = 1 \
375 }
376
377/**
378 * @brief Declares one transfer that reads a pool and writes a peripheral.
379 *
380 * @param name_ Name the descriptor is reached by.
381 * @param from_pool_ Pool the bytes are read from.
382 * @param from_offset_ Bytes into that pool the read starts at.
383 * @param peripheral_ Peripheral declared by PraetPeripheralDeclare.
384 * @param address_ Base of that peripheral, stated again here as the vendor's own name.
385 * @param at_offset_ Bytes into the peripheral block the transfer writes at.
386 * @param bytes_ Bytes to move.
387 * @param mensura_ Bytes moved in one step, as a EgoSumMensura.
388 * @param next_ The descriptor that runs after this one, or NULL.
389 * @note The source advances and the destination stays where it is. This alone makes a
390 * transfer a write to a peripheral, and it is the single one fact memcpy never needs.
391 * @note Neither way to walk, because a pool and a register are not the same object and a transfer
392 * between them cannot read what it has written.
393 * @note The address is named at the declaration and named again here, and that is how PraetSubmit
394 * already treats a pool: the pool is named a second time so the extent is worked out from the
395 * declaration instead of carried around. The enumerator below is the lock and this expression
396 * is the key.
397 * @note Stating it again is also what keeps the initializer legal. An address constant plus an
398 * integer constant is still an address constant, while reading the value out of the pointer
399 * PraetPeripheralDeclare emits is not one at all, whatever a given compiler folds.
400 * @warning A base plus an offset, and so one declared peripheral covers every register in its block. The
401 * offset is bounded by nothing here, because a peripheral has no extent to measure against.
402 */
403#define PraetToPeripheral(name_, from_pool_, from_offset_, peripheral_, address_, at_offset_, bytes_, mensura_, next_) \
404 PRAET_STEPS_FIT(#name_ " reading from " #from_pool_, from_pool_, from_offset_, bytes_, mensura_); \
405 PRAET_DESCRIPTOR_BODY(name_, &mmgr_pars_storage_##from_pool_[from_offset_], PRAET_ADDRESS_ADVANCES, \
406 ((uint8_t *)(address_) + (at_offset_)), PRAET_ADDRESS_FIXED, bytes_, mensura_, \
407 PRAET_MOTUS_NEUTRUM, next_); \
408 enum \
409 { \
410 name_##_writes_##peripheral_ = peripheral_##_is_a_declared_peripheral \
411 }
412
413/**
414 * @brief Declares one transfer that reads a peripheral and writes a pool.
415 *
416 * @param name_ Name the descriptor is reached by.
417 * @param peripheral_ Peripheral declared by PraetPeripheralDeclare.
418 * @param address_ Base of that peripheral, stated again here as the vendor's own name.
419 * @param at_offset_ Bytes into the peripheral block the transfer reads at.
420 * @param to_pool_ Pool the bytes are written to.
421 * @param to_offset_ Bytes into that pool the write starts at.
422 * @param bytes_ Bytes to move.
423 * @param mensura_ Bytes moved in one step, as a EgoSumMensura.
424 * @param next_ The descriptor that runs after this one, or NULL.
425 * @note The mirror of PraetToPeripheral. The source stays where it is and the destination advances,
426 * and that leaves a transfer a read from a peripheral.
427 * @note The address is named at the declaration and named again here, for the reasons written out
428 * over PraetToPeripheral.
429 */
430#define PraetFromPeripheral(name_, peripheral_, address_, at_offset_, to_pool_, to_offset_, bytes_, mensura_, next_) \
431 PRAET_STEPS_FIT(#name_ " writing to " #to_pool_, to_pool_, to_offset_, bytes_, mensura_); \
432 PRAET_DESCRIPTOR_BODY(name_, ((const uint8_t *)(address_) + (at_offset_)), PRAET_ADDRESS_FIXED, \
433 &mmgr_pars_storage_##to_pool_[to_offset_], PRAET_ADDRESS_ADVANCES, bytes_, mensura_, \
434 PRAET_MOTUS_NEUTRUM, next_); \
435 enum \
436 { \
437 name_##_reads_##peripheral_ = peripheral_##_is_a_declared_peripheral \
438 }
439
440/**
441 * @brief Counts the descriptors a chain reaches before it repeats or ends.
442 *
443 * @param[in] first First descriptor in the chain [BORROWS].
444 * @param[in] limit How many links to follow before giving up.
445 * @return Descriptors reached, or @p limit where the chain is longer than that.
446 * @note What tells a one shot from a cycle without following a cycle forever. A chain that ends
447 * returns its length; a chain that closes returns the number of distinct descriptors in it.
448 * @note Walks by pointer identity against the first descriptor alone, and so it names a cycle that
449 * returns to the head and not one that closes further along. A chain that loops back to its
450 * middle reaches the limit, the honest answer for a walk that cannot see where it is.
451 */
452embed_word praet_descriptor_chain_length(const PraetDescriptor *first, embed_word limit);
453
454EMBED_END_DECLS
455
456#endif
EgoSumMensura
Bytes the engine moves in one step, the measure everything else is counted in.
@ PRAET_MENSURA_VERBUM
@ PRAET_MENSURA_SEMIVERBUM
@ PRAET_MENSURA_OCTETUS
DirectioMotusVerbi
Which way a transfer walks the words it moves.
@ PRAET_MOTUS_DEORSUM
@ PRAET_MOTUS_SURSUM
@ PRAET_MOTUS_NEUTRUM
PraetDescriptorOwner
Who holds a descriptor.
@ PRAET_OWNER_SOFTWARE
@ PRAET_OWNER_ENGINE
PraetAddressing
How an address behaves as a transfer runs.
@ PRAET_ADDRESS_FIXED
@ PRAET_ADDRESS_ADVANCES
embed_word praet_descriptor_chain_length(const PraetDescriptor *first, embed_word limit)
Counts the descriptors a chain reaches before it repeats or ends.
The schedule context: one flag word per channel, the two volatiles that coordinate the interrupt,...
One transfer, written down.