MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
praet_descriptor.h File Reference

A transfer written down: where from, where to, how many bytes, how each address moves, and what runs after it. More...

Go to the source code of this file.

Data Structures

struct  PraetDescriptor
 One transfer, written down. More...

Macros

#define PRAET_MENSURA_MAXIMA
 The largest measure this module knows about.
#define PRAET_DIRECTIO_MOTUS_VERBI_FOR(from_, to_, bytes_)
 The direction of the movement of the word, for two spans of one pool.
#define PRAET_STEPS_FIT(what_, pool_, offset_, bytes_, mensura_)
 Fails the build where one end of a transfer does not sit whole steps inside its pool.
#define PRAET_DESCRIPTOR_BODY(name_, source_, source_mode_, dest_, dest_mode_, bytes_, mensura_, motus_, next_)
 Declares one memory to memory transfer, and what runs after it.
#define PraetDescriptorDeclare(name_, from_pool_, from_offset_, to_pool_, to_offset_, bytes_, mensura_, motus_, next_)
 Declares one memory to memory transfer, and what runs after it.
#define PRAET_DESCRIPTOR_ENDS_DIFFER(name_, from_pool_, to_pool_)
 Fails the build where a transfer between two pools names one pool twice.
#define PraetOneShot(name_, from_pool_, from_offset_, to_pool_, to_offset_, bytes_, mensura_)
 Declares one transfer that runs once and stops.
#define PraetDescriptorWithin(name_, pool_, from_offset_, to_offset_, bytes_, mensura_, next_)
 Declares one transfer that moves bytes inside a single pool, and works out which way.
#define PraetCircular(name_, from_pool_, from_offset_, to_pool_, to_offset_, bytes_, mensura_)
 Declares one transfer that runs itself again, forever.
#define PraetPingPong(first_, second_, from_pool_, to_pool_, first_from_, first_to_, second_from_, second_to_, bytes_, mensura_)
 Declares two transfers that hand off to each other, forever.
#define PraetPeripheralDeclare(name_, address_)
 Declares one peripheral register a transfer may reach.
#define PraetToPeripheral(name_, from_pool_, from_offset_, peripheral_, address_, at_offset_, bytes_, mensura_, next_)
 Declares one transfer that reads a pool and writes a peripheral.
#define PraetFromPeripheral(name_, peripheral_, address_, at_offset_, to_pool_, to_offset_, bytes_, mensura_, next_)
 Declares one transfer that reads a peripheral and writes a pool.

Enumerations

enum  PraetAddressing {
  PRAET_ADDRESS_ADVANCES ,
  PRAET_ADDRESS_FIXED
}
 How an address behaves as a transfer runs. More...
enum  EgoSumMensura {
  PRAET_MENSURA_OCTETUS ,
  PRAET_MENSURA_SEMIVERBUM ,
  PRAET_MENSURA_VERBUM
}
 Bytes the engine moves in one step, the measure everything else is counted in. More...
enum  DirectioMotusVerbi {
  PRAET_MOTUS_NEUTRUM ,
  PRAET_MOTUS_SURSUM ,
  PRAET_MOTUS_DEORSUM
}
 Which way a transfer walks the words it moves. More...
enum  PraetDescriptorOwner {
  PRAET_OWNER_SOFTWARE ,
  PRAET_OWNER_ENGINE
}
 Who holds a descriptor. More...

Functions

embed_word praet_descriptor_chain_length (const PraetDescriptor *first, embed_word limit)
 Counts the descriptors a chain reaches before it repeats or ends.

Detailed Description

A transfer written down: where from, where to, how many bytes, how each address moves, and what runs after it.

Author
dstroy0 (Douglas Quigg) dquig.nosp@m.g123.nosp@m.@gmai.nosp@m.l.co.nosp@m.m
Date
2026-09-01
Note
The shape three vendors already agree on. An ESP32 GDMA dma_descriptor_t, an i.MX RT eDMA TCD and a Zephyr dma_block_config each carry where from, where to, how much, how the addresses move, who holds it, and what is next. Three architectures, one descriptor.
Descriptors link, and that is where the arrangements come from. Two whose next point at each other are ping-pong. One pointing at itself is circular. A chain is scatter-gather. A next of NULL is one shot. No part has a ping-pong feature and neither does this.
Who holds a descriptor is per descriptor and not per channel. A channel's own state is the quaternary core in its flag word, and that answers a different question: the core says what the channel is doing, and an owner says which link in the chain the engine is on.
A peripheral end is an address that does not advance. Where that address is comes from the vendor's own header, which the compiler has already read. A caller names theirs and no number of any part appears in this tree.

Definition in file praet_descriptor.h.

Macro Definition Documentation

◆ PRAET_MENSURA_MAXIMA

#define PRAET_MENSURA_MAXIMA

The largest measure this module knows about.

Note
Raise this when a wider step is added, and the assertion on a descriptor's measure stops one being written that nothing here can carry.

Definition at line 72 of file praet_descriptor.h.

◆ PRAET_DIRECTIO_MOTUS_VERBI_FOR

#define PRAET_DIRECTIO_MOTUS_VERBI_FOR ( from_,
to_,
bytes_ )

The direction of the movement of the word, for two spans of one pool.

Parameters
from_Bytes into the pool the read starts at.
to_Bytes into the pool the write starts at.
bytes_Bytes moved.
Returns
A DirectioMotusVerbi.
Note
Integers, and so this is answered while compiling. Both spans are offsets into one declared object, and where they came from makes them comparable at all: two addresses handed in as pointers could not be ordered at compile time, and comparing pointers into separate objects is not something the language defines.
Writing upward over a source means the high words must move first, and so the walk goes downward. Writing downward is the mirror of it. Spans that start at the same place move nothing over anything and take neither.

Definition at line 107 of file praet_descriptor.h.

◆ PRAET_STEPS_FIT

#define PRAET_STEPS_FIT ( what_,
pool_,
offset_,
bytes_,
mensura_ )

Fails the build where one end of a transfer does not sit whole steps inside its pool.

Parameters
what_Text naming which end this is, for the message.
pool_Pool declared by ParsMemoriaeInternae or ParsMemoriaeExternum.
offset_Bytes into that pool the span starts at.
bytes_Bytes in the span.
mensura_Bytes the engine moves in one step.
Note
Three conditions in one assertion, because they fail for one reason and a caller fixing one wants to see the others. The span fits the pool, the offset lands on a step boundary, and the length is a whole number of steps.
Static assertions, where PraetSubmit uses the expression form. A declarator is at file scope and a bare expression cannot sit there. Naming the end in the message is what replaces the member name the expression form prints.
Warning
Reads sizeof(mmgr_pars_storage_##pool_), and so it holds for a span whose offset, length and width are known while compiling. Anything computed at run time is not bounded by this and is not bounded anywhere else.

Definition at line 177 of file praet_descriptor.h.

◆ PRAET_DESCRIPTOR_BODY

#define PRAET_DESCRIPTOR_BODY ( name_,
source_,
source_mode_,
dest_,
dest_mode_,
bytes_,
mensura_,
motus_,
next_ )

Declares one memory to memory transfer, and what runs after it.

Parameters
name_Name the descriptor is reached by.
from_pool_Pool the bytes are read from.
from_offset_Bytes into that pool the read starts at.
to_pool_Pool the bytes are written to.
to_offset_Bytes into that pool the write starts at.
bytes_Bytes to move.
mensura_Bytes moved in one step, as a EgoSumMensura.
next_The descriptor that runs after this one, or NULL.
Note
Both pools are named, and so both spans are proved to fit before anything runs and both addresses come from a declaration, with no caller stating one. That is the same check PraetAttach makes, applied to each end.
Both addresses advance, as memory to memory means. A side that stays put is a peripheral, and naming one needs an address this has no way to take.
Emitted as initialized data with the owner in software's hands. A descriptor the engine has not been given is one the program may still write.
Two different pools are two separately declared objects, and so they cannot overlap and the direction is settled as either. Naming one pool at both ends collides on the enumerators this emits, because the two carry the same prefix and differ only by the pool. A transfer inside one pool is PraetDescriptorWithin, which has offsets it can compare.
Warning
direction_ is stated here and derived by the two macros above this one. Reaching this directly with the wrong answer is a transfer that reads bytes it has already written over.

Definition at line 208 of file praet_descriptor.h.

◆ PraetDescriptorDeclare

#define PraetDescriptorDeclare ( name_,
from_pool_,
from_offset_,
to_pool_,
to_offset_,
bytes_,
mensura_,
motus_,
next_ )

Declares one memory to memory transfer, and what runs after it.

Parameters
name_Name the descriptor is reached by.
from_pool_Pool the bytes are read from.
from_offset_Bytes into that pool the read starts at.
to_pool_Pool the bytes are written to.
to_offset_Bytes into that pool the write starts at.
bytes_Bytes to move.
mensura_Bytes moved in one step, as a EgoSumMensura.
motus_Which way the words are walked, as a DirectioMotusVerbi.
next_The descriptor that runs after this one, or NULL.

Definition at line 237 of file praet_descriptor.h.

◆ PRAET_DESCRIPTOR_ENDS_DIFFER

#define PRAET_DESCRIPTOR_ENDS_DIFFER ( name_,
from_pool_,
to_pool_ )

Fails the build where a transfer between two pools names one pool twice.

Parameters
name_Name of the descriptor this belongs to.
from_pool_Pool the bytes are read from.
to_pool_Pool the bytes are written to.
Note
Two enumerators carrying one prefix and differing only by the pool, and so naming one pool at both ends declares the same enumerator twice in one enum and the compiler refuses it. The same idiom MMGR_PARS_CLAIMED_ONCE uses, applied to a pair.
Worth refusing because the direction a transfer between two pools takes is settled as either, and that answer is only true because two declared pools are two objects. One pool at both ends can overlap itself, and PraetDescriptorWithin is what has the offsets to work that out.

Definition at line 257 of file praet_descriptor.h.

◆ PraetOneShot

#define PraetOneShot ( name_,
from_pool_,
from_offset_,
to_pool_,
to_offset_,
bytes_,
mensura_ )

Declares one transfer that runs once and stops.

Parameters
name_Name the descriptor is reached by.
from_pool_Pool the bytes are read from.
from_offset_Bytes into that pool the read starts at.
to_pool_Pool the bytes are written to.
to_offset_Bytes into that pool the write starts at.
bytes_Bytes to move.
mensura_Bytes moved in one step.
Note
A next of NULL, the entirety of of what makes a transfer one shot.

Definition at line 276 of file praet_descriptor.h.

◆ PraetDescriptorWithin

#define PraetDescriptorWithin ( name_,
pool_,
from_offset_,
to_offset_,
bytes_,
mensura_,
next_ )

Declares one transfer that moves bytes inside a single pool, and works out which way.

Parameters
name_Name the descriptor is reached by.
pool_Pool both ends sit in.
from_offset_Bytes into that pool the read starts at.
to_offset_Bytes into that pool the write starts at.
bytes_Bytes to move.
mensura_Bytes moved in one step, as a EgoSumMensura.
next_The descriptor that runs after this one, or NULL.
Note
Where the two spans overlap, the direction keeps the transfer from reading bytes it has already written over. It is worked out here from the offsets, once, and never asked again: a run of this descriptor has the answer in front of it.
Both spans are offsets into one declared object, and that leaves them comparable at all. The same question about two pointers could not be answered while compiling, and comparing pointers into separate objects is not something the language defines.

Definition at line 298 of file praet_descriptor.h.

◆ PraetCircular

#define PraetCircular ( name_,
from_pool_,
from_offset_,
to_pool_,
to_offset_,
bytes_,
mensura_ )

Declares one transfer that runs itself again, forever.

Parameters
name_Name the descriptor is reached by.
from_pool_Pool the bytes are read from.
from_offset_Bytes into that pool the read starts at.
to_pool_Pool the bytes are written to.
to_offset_Bytes into that pool the write starts at.
bytes_Bytes to move.
mensura_Bytes moved in one step.
Note
A descriptor whose next is itself. This defines circular, and no part has a feature called that either.
The forward declaration ahead of it is a tentative definition, which C merges with the real one below. It lets the initializer name an address the declaration is still writing.

Definition at line 317 of file praet_descriptor.h.

◆ PraetPingPong

#define PraetPingPong ( first_,
second_,
from_pool_,
to_pool_,
first_from_,
first_to_,
second_from_,
second_to_,
bytes_,
mensura_ )

Declares two transfers that hand off to each other, forever.

Parameters
first_Name the first descriptor is reached by.
second_Name the second descriptor is reached by.
from_pool_Pool both transfers read from.
to_pool_Pool both transfers write to.
first_from_Bytes into the source pool the first transfer reads at.
first_to_Bytes into the destination pool the first transfer writes at.
second_from_Bytes into the source pool the second transfer reads at.
second_to_Bytes into the destination pool the second transfer writes at.
bytes_Bytes each transfer moves.
mensura_Bytes moved in one step.
Note
Ping-pong, and it is two descriptors whose next point at each other. Nothing here is a mode and no part has a bit for it. What makes it double buffering is that the two write different halves, the caller's to arrange by the offsets they pass.
Both spans are proved against their pools separately. A pair whose halves overlap or run off the end fails at the declaration that wrote it.

Definition at line 342 of file praet_descriptor.h.

◆ PraetPeripheralDeclare

#define PraetPeripheralDeclare ( name_,
address_ )

Declares one peripheral register a transfer may reach.

Parameters
name_Name the peripheral is reached by.
address_Where the register is, as the vendor's own header gives it.
Note
The address is not this library's to know and it is not the caller's to invent. Every part ships a header naming its registers, the compiler has read it, and what a caller writes here is that name. An ESP32 reaches one through its GDMA peripheral struct, an i.MX RT through LPUART1->DATA, and neither number appears in this tree.
Emits a pointer under a mangled name and an enumerator carrying the peripheral's. A descriptor naming a peripheral pastes onto both. Anything that was not declared here fails on a name nobody wrote. The same proof a pool gives, for a thing that has no extent.
volatile, because a register is read and written by something other than this program and a compiler that cached one would be reading a value the part has moved on from.
Warning
No extent, the point. A peripheral is one address that stays where it is, and so there is nothing for a span check to measure and a transfer's length is bounded by the pool at its other end alone.

Definition at line 370 of file praet_descriptor.h.

◆ PraetToPeripheral

#define PraetToPeripheral ( name_,
from_pool_,
from_offset_,
peripheral_,
address_,
at_offset_,
bytes_,
mensura_,
next_ )

Declares one transfer that reads a pool and writes a peripheral.

Parameters
name_Name the descriptor is reached by.
from_pool_Pool the bytes are read from.
from_offset_Bytes into that pool the read starts at.
peripheral_Peripheral declared by PraetPeripheralDeclare.
address_Base of that peripheral, stated again here as the vendor's own name.
at_offset_Bytes into the peripheral block the transfer writes at.
bytes_Bytes to move.
mensura_Bytes moved in one step, as a EgoSumMensura.
next_The descriptor that runs after this one, or NULL.
Note
The source advances and the destination stays where it is. This alone makes a transfer a write to a peripheral, and it is the single one fact memcpy never needs.
Neither way to walk, because a pool and a register are not the same object and a transfer between them cannot read what it has written.
The address is named at the declaration and named again here, and that is how PraetSubmit already treats a pool: the pool is named a second time so the extent is worked out from the declaration instead of carried around. The enumerator below is the lock and this expression is the key.
Stating it again is also what keeps the initializer legal. An address constant plus an integer constant is still an address constant, while reading the value out of the pointer PraetPeripheralDeclare emits is not one at all, whatever a given compiler folds.
Warning
A base plus an offset, and so one declared peripheral covers every register in its block. The offset is bounded by nothing here, because a peripheral has no extent to measure against.

Definition at line 403 of file praet_descriptor.h.

◆ PraetFromPeripheral

#define PraetFromPeripheral ( name_,
peripheral_,
address_,
at_offset_,
to_pool_,
to_offset_,
bytes_,
mensura_,
next_ )

Declares one transfer that reads a peripheral and writes a pool.

Parameters
name_Name the descriptor is reached by.
peripheral_Peripheral declared by PraetPeripheralDeclare.
address_Base of that peripheral, stated again here as the vendor's own name.
at_offset_Bytes into the peripheral block the transfer reads at.
to_pool_Pool the bytes are written to.
to_offset_Bytes into that pool the write starts at.
bytes_Bytes to move.
mensura_Bytes moved in one step, as a EgoSumMensura.
next_The descriptor that runs after this one, or NULL.
Note
The mirror of PraetToPeripheral. The source stays where it is and the destination advances, and that leaves a transfer a read from a peripheral.
The address is named at the declaration and named again here, for the reasons written out over PraetToPeripheral.

Definition at line 430 of file praet_descriptor.h.

Enumeration Type Documentation

◆ PraetAddressing

How an address behaves as a transfer runs.

Note
The one fact memcpy does not need. Memory to memory advances both sides; a peripheral is an address that stays where it is, and which side stays put makes a transfer a read or a write.
Token ids, the same as the statuses and the regions. A mistyped name is an undeclared identifier carrying the name that was written.
Enumerator
PRAET_ADDRESS_ADVANCES 

The address moves on by the transfer width each step.

PRAET_ADDRESS_FIXED 

The address stays where it is, as a peripheral is.

Definition at line 43 of file praet_descriptor.h.

◆ EgoSumMensura

Bytes the engine moves in one step, the measure everything else is counted in.

Note
Named in full for what it is. A transfer's offsets, its length and the boundary a recovery rounds to are all counted in this, and so it is the measure and not a property beside the others.
Carries the count itself, and so the assertions below divide a length by it and test an offset against it with no table in between.
octetus and verbum are the tree's own words for a byte and a word, as in octetus_introitus_exitus and verba_scribo.
Enumerator
PRAET_MENSURA_OCTETUS 

One byte a step.

PRAET_MENSURA_SEMIVERBUM 

Half a word a step.

PRAET_MENSURA_VERBUM 

A whole word a step.

Definition at line 59 of file praet_descriptor.h.

◆ DirectioMotusVerbi

Which way a transfer walks the words it moves.

Note
Named in full, because a bare direction says nothing about what is moving in it. This is the direction of the movement of the word, and the word is what the engine steps by.
What memor already answers for a copy, and for the same reason. Where the two ends overlap, one direction reads words the other has already written over. Where they do not, neither direction is wrong and the engine takes whichever it likes.
Settled once and then not asked again. Here it is settled before anything runs, because both ends of a descriptor come from a declaration.
Enumerator
PRAET_MOTUS_NEUTRUM 

Neither way. The ends do not overlap, and so nothing is at risk.

PRAET_MOTUS_SURSUM 

Upward, from the low word on, which is safe writing downward.

PRAET_MOTUS_DEORSUM 

Downward, from the high word back, which is safe writing upward.

Definition at line 85 of file praet_descriptor.h.

◆ PraetDescriptorOwner

Who holds a descriptor.

Note
Per descriptor. An ESP32 descriptor calls this owner, an eDMA carries DONE and ACTIVE in its control word, and a Zephyr block config calls it busy. They are the same fact.
Enumerator
PRAET_OWNER_SOFTWARE 

The program may read it and write it.

PRAET_OWNER_ENGINE 

The engine is working from it,, alone may touch it.

Definition at line 118 of file praet_descriptor.h.

Function Documentation

◆ praet_descriptor_chain_length()

embed_word praet_descriptor_chain_length ( const PraetDescriptor * first,
embed_word limit )

Counts the descriptors a chain reaches before it repeats or ends.

Parameters
[in]firstFirst descriptor in the chain [BORROWS].
[in]limitHow many links to follow before giving up.
Returns
Descriptors reached, or limit where the chain is longer than that.
Note
What tells a one shot from a cycle without following a cycle forever. A chain that ends returns its length; a chain that closes returns the number of distinct descriptors in it.
Walks by pointer identity against the first descriptor alone, and so it names a cycle that returns to the head and not one that closes further along. A chain that loops back to its middle reaches the limit, the honest answer for a walk that cannot see where it is.

References praet_descriptor_chain_length().

Referenced by praet_descriptor_chain_length().