3. RTT map/unmap flows
This document describes the design of the range-aware RTT map and unmap
commands implemented in runtime/rmi/rtt_map.c and
runtime/rmi/rtt_unmap.c. It focuses on the common flow first, then
describes how the DATA, UNPROT and DEV flavours differ.
The commands covered here are:
RMI_RTT_DATA_MAPandRMI_RTT_DATA_UNMAPfor protected Realm data.RMI_RTT_UNPROT_MAPandRMI_RTT_UNPROT_UNMAPfor unprotected NS mappings outside the Realm’s PAR.RMI_RTT_DEV_MAPandRMI_RTT_DEV_UNMAPfor device memory mappings.RMI_RTT_DATA_MAP_INITas the single-page, measurement-aware data initialization path.
The basic invariant is that an RTT leaf entry is made visible as a usable mapping only after its required ownership, cache and TLB maintenance is complete. If that work must continue after the command returns, the entry carries a software marker that blocks conflicting operations until completion.
3.2. Map flow
High-level map flow
Host RMI_RTT_*_MAP(base, top, descriptors)
|
v
Validate RD state, descriptor source, IPA range and PAR side
|
v
Decode first descriptor level and walk to the leaf containing base
|
v
For each entry in that leaf while IPA < top:
pop one descriptor and run per-block checks
DATA/DEV: stamp drain-pending entry and drain backing granules
yield via SRO if an IRQ is pending
UNPROT: write assigned_ns entry synchronously
advance out_top
|
v
Return a no-progress error, RMI_SUCCESS + out_top for terminal
progress, or RMI_INCOMPLETE + SRO handle for a yielded DATA/DEV drain
DATA/DEV map continue flow
Host RMI_OP_CONTINUE(handle)
|
v
Find the sealed SRO context and dispatch to the DATA/DEV map continue handler
|
v
Re-lock the RD and pinned leaf RTT, then map the leaf with the Realm MECID
|
v
Resume the drain from the saved cursor:
DATA: zero and transition backing granules from DELEGATED to DATA
DEV: transition dev_granules from DELEGATED to MAPPED, checking
that all granules in the block have the same coherency type
|
v
If an IRQ is pending:
return RMI_INCOMPLETE and keep the drain-pending marker
If rollback completes after a drain error:
clear the marker, drop the leaf refcount and return error/partial progress
If the drain completes:
replace the marker with the assigned entry and return RMI_SUCCESS + next IPA
3.2.1. Common validation
The range map commands share a validator. It checks that the Realm is NEW
or ACTIVE, that top is greater than base, and that the descriptor
source is valid. The first descriptor is decoded to get the walk level and
block size.
The IPA range is then checked against the Realm IPA size and PAR:
DATA and DEV map require the entire range to be inside the PAR.
UNPROT map requires the entire range to be outside the PAR.
Per-block checks are done inside the walk loop because they depend on the
current descriptor and the remaining IPA range. Each block must match the call
level, fit before top, be aligned to the block size, and fit within the PA
width when LPA2 is disabled. DATA and DEV map also require the expected
descriptor operation state; UNPROT map validates the resulting host NS S2TTE.
3.2.2. Common execution
After validation, the command locks the root RTT, releases the RD lock, walks
to base at the requested level, and maps the leaf RTT with the Realm MECID.
The loop then consumes one block descriptor per leaf slot until it reaches the
end of the leaf, the requested top, the descriptor list, an error, or a
yield point.
RMI_RTT_DATA_MAP and RMI_RTT_DEV_MAP reserve an SRO context before
validation because one block can take multiple per-granule transitions. If
they yield, the partially prepared block is resumed by RMI_OP_CONTINUE.
RMI_RTT_UNPROT_MAP has no SRO lifecycle. Each block is completed by one
leaf S2TTE write, so a pending IRQ only stops the loop and returns terminal
progress.
3.2.3. RMI_RTT_DATA_MAP_INIT
RMI_RTT_DATA_MAP_INIT is the initialization-time special case. It maps one
delegated granule at page level, copies data from an NS source granule,
optionally measures the content, writes an assigned_ram S2TTE, takes the
leaf RTT refcount, and transitions the backing granule to DATA. It only accepts
a Realm in NEW state and does not use the range/SRO map machinery.
3.3. Unmap flow
High-level unmap flow
Host RMI_RTT_*_UNMAP(base, top, output mode)
|
v
Validate IPA range, PAR side and output-address mode
|
v
Reserve SRO, snapshot S2 context / VMIDs, then walk to cur_base
|
v
Sweep one leaf RTT:
skip non-live entries, including drain-pending markers
for live entries owned by the selected flavor:
append the old OA to the output list
write the matching unassigned_* entry with a drain-pending marker
stamp TLBI_PENDING if the old entry was HW-valid
advance cur_base
|
v
Run deferred pipeline:
TLBI pass for marked entries
DATA/DEV backing-granule drain; UNPROT skips this phase
clear drain markers and drop leaf refcounts
|
v
Return a first-entry error, RMI_SUCCESS + out_top/output descriptors,
or RMI_INCOMPLETE + SRO handle if deferred work yields
Unmap continue flow
Host RMI_OP_CONTINUE(handle)
|
v
Find the sealed SRO context and dispatch to the unmap continue handler
|
v
Re-lock the pinned leaf RTT and map it with the cached Realm MECID
|
v
Resume the deferred pipeline:
TLBI pass for entries with this SRO handle and TLBI_PENDING
DATA/DEV: transition queued backing granules back to DELEGATED
UNPROT: skip backing-granule drain
clear drain-pending markers and drop leaf refcounts
|
v
If an IRQ is pending during TLBI or backing-granule drain:
return RMI_INCOMPLETE and keep the SRO context sealed
If the pipeline completes:
format output descriptors from the saved list and return RMI_SUCCESS + out_top
3.3.1. Sweep phase
The common unmap runner walks to cur_base and sweeps one leaf table. For
each live entry owned by the selected flavour, it:
Extracts the old OA and appends it to the SRO output address list.
Replaces the live entry with the appropriate unassigned form.
Stamps the drain-pending marker and SRO handle.
Stamps
TLBI_PENDINGwhen the old entry had a hardware-valid mapping.Advances
cur_baseto the next IPA.
Non-live entries are skipped. If no live entry was unmapped, the result still
advances x[1] over the contiguous run of non-live entries in the leaf so
the host can skip unused IPA ranges efficiently.
Some stop conditions are treated differently depending on whether they happen
on the first entry of the sweep. A table descriptor at a non-leaf level, a live
entry larger than the remaining range, a DATA entry with live auxiliary
mappings, or a wrong-flavour live entry is reported as an error when it is the
first entry. After prior progress or skipped entries, the sweep stops with
success and the host can reissue from the returned cur_base to observe the
condition as a first-entry error.
3.3.2. Deferred phase
After the sweep, all unmap flavours run the same deferred pipeline:
Issue pending stage-2 TLB invalidations, and SMMU invalidations when device assignment is enabled, for entries marked with
S2TTE_SW_TLBI_PENDING_BITand a matchingS2TTE_SW_HANDLE. Clear the TLBI-pending bit after each invalidation.Drain backing granules for DATA and DEV unmap. DATA transitions DATA granules back to DELEGATED with the required cache maintenance. DEV transitions MAPPED dev granules back to DELEGATED. UNPROT has no backing granule drain.
Clear the drain-pending markers and drop the leaf RTT refcount associated with each stamped entry whose
S2TTE_SW_HANDLEmatches the current SRO.
The first two phases are yieldable and sample for pending IRQs. If a yield
occurs, the SRO context keeps the output address list, drain cursors and leaf
metadata. RMI_OP_CONTINUE re-locks the leaf and resumes the same pipeline.
3.3.3. Result formatting
Terminal unmap success is formatted from the output address list:
NONEdiscards the list and reports onlyx[1].SINGLEencodes the first descriptor intox[2]. The sweep stops at a PA discontinuity so the single descriptor remains contiguous.LISTcopies as many descriptors as allowed to the host’s NS output buffer and reports the copied count inx[3].
If the final copy to an NS list fails because the host changed the buffer’s state after validation, the unmap is not rolled back. The command reports zero copied descriptors.
3.4. Flavour-specific detail
3.4.1. DATA map
DATA map consumes descriptors whose state is RMI_OP_MEM_DELEGATED and maps
them inside the PAR. A new mapping starts from an unassigned leaf entry. The
entry is stamped with the SRO handle and drain-pending bit, then each backing
granule is locked in DELEGATED state, zeroed under the Realm MECID, and
transitioned to DATA.
When all granules in the block have been drained, the leaf entry is finalized
with s2tte_create_assigned_unchanged() for the requested OA. If a backing
granule cannot be claimed, already transitioned granules are rolled back to
DELEGATED, the marker is cleared, and the leaf refcount is dropped.
An existing DATA mapping to the same OA is idempotent. An existing mapping to a different OA, a non-unassigned entry, or an entry with a pending drain produces an error or busy result.
3.4.2. DATA unmap
DATA unmap accepts live assigned_ram, assigned_empty and
assigned_destroyed entries. It rejects device entries, because those must
be unmapped through the DEV command, and it rejects entries that still have
live auxiliary RTT mappings.
The replacement S2TTE preserves the DATA RIPAS state:
assigned_rambecomesunassigned_destroyedand owes TLBI.assigned_emptybecomesunassigned_emptyand does not owe TLBI.assigned_destroyedbecomesunassigned_destroyedand does not owe TLBI.
After TLBI, every freed DATA granule is transitioned back to DELEGATED with cache maintenance. The old live mapping refcount on the leaf is dropped only after this drain and marker clearing are complete.
3.4.3. UNPROT map
UNPROT map maps descriptors outside the PAR. The current implementation does
not enforce the descriptor state field for this flavour. Instead, it builds a
constant attribute mask from MEMATTR and S2AP flags, using either
direct AP bits or indirect permission indexes according to the Realm S2
context. Each block combines that mask with the descriptor PA and validates the
resulting host NS S2TTE.
The leaf entry must be unassigned_ns. A matching assigned_ns entry is
idempotent. There is no backing granule transition and no leaf refcount for the
NS memory itself; architectural protection prevents an NS PA from being used as
protected memory at the same time.
3.4.4. UNPROT unmap
UNPROT unmap accepts assigned_ns entries outside the PAR. Each freed
entry becomes unassigned_ns and always owes TLBI because assigned_ns is
a hardware-valid mapping.
There is no backing granule drain. The sweep takes a transient leaf refcount for each stamped entry so the leaf remains pinned across any yield in the TLBI pass. Those transient refcounts are dropped when the drain-pending markers are cleared.
3.4.5. DEV map
DEV map consumes descriptors whose state is RMI_OP_MEM_DELEGATED and maps
them inside the PAR. It also validates the VDEV granule, verifies that the
VDEV belongs to the target RD, and snapshots the VDEV address ranges before
walking the RTT. Every mapped block must fit entirely inside one of those VDEV
ranges.
The leaf entry must be unassigned_empty or unassigned_destroyed. The
entry is stamped with the SRO handle and drain-pending bit, then each backing
dev_granule is locked in DELEGATED state and transitioned to MAPPED. The
coherency type of the first dev_granule is recorded, and every later
granule in the block must match it.
When the block drain completes, the leaf entry is finalized with
s2tte_create_assigned_dev_unchanged(). If any backing dev granule cannot be
claimed, or if coherency types differ within the block, already transitioned
dev granules are rolled back to DELEGATED and the marker is cleared.
3.4.6. DEV unmap
DEV unmap uses the common unmap runner but selects the DEV flavour. The sweep
accepts assigned_dev_dev, assigned_dev_empty and
assigned_dev_destroyed entries at any supported device mapping level. Other
live states produce RMI_ERROR_RTT(level) when they are encountered as the
first entry of the sweep.
The replacement S2TTE mirrors the DATA RIPAS-style behaviour for device entries:
assigned_dev_devbecomesunassigned_destroyedand owes TLBI.assigned_dev_emptybecomesunassigned_emptyand does not owe TLBI.assigned_dev_destroyedbecomesunassigned_destroyedand does not owe TLBI.
After TLBI, every freed MAPPED dev granule is transitioned back to DELEGATED. No data-cache maintenance is required for device memory, but the drain still uses the same cooperative yield structure as DATA unmap.